Merge upstream changes from 2026-05-31

This commit is contained in:
2026-05-31 11:23:38 -04:00
148 changed files with 8048 additions and 8319 deletions
-1
View File
@@ -81,7 +81,6 @@ set(SOURCES
src/Episode3/RulerServer.cc
src/Episode3/Server.cc
src/Episode3/Tournament.cc
src/FileContentsCache.cc
src/GameServer.cc
src/GSLArchive.cc
src/HTTPServer.cc
+1 -4
View File
@@ -1,13 +1,11 @@
## General
- 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
- 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 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)
- 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.)
## PSO DC
@@ -31,5 +29,4 @@
## PSOBB
- Make server-specified rare enemies work with maps loaded by the proxy
- Record some BB tests
- Add all necessary Guild Card number rewrites in BB commands on the proxy
- Implement BB replay tests properly and record some
+13
View File
@@ -41,6 +41,7 @@ Version codes (from README.md):
4OEU: PSO Xbox US TU
4OPD: PSO Xbox EU Disc
4OPU: PSO Xbox EU TU
50YJ: PSO BB US 1.24.3
59NJ: PSO BB JP 1.25.11
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
3OJT => 0415BF50 38000005
3OP0 => 041052D4 38000005
5OYJ => 005C8C8F E9A7000000
59NJ => 005C9F35 E9A7000000
59NL => 005C9F31 E9A7000000
@@ -88,6 +90,7 @@ All items visible in Pioneer 2
3OE1 => 04102D88 38600000
Mags visible in Pioneer 2
5OYJ => 005D7053 EB04
59NJ => 005D8F27 EB04
59NL => 005D8F4B EB04
@@ -96,6 +99,9 @@ Disable pause menu background + offset
0428735C 4800000C
3OE2 => 0424CED8 48000370
042887D8 4800000C
5OYJ => 00713758 9090
0072D417 9090
0072D27E 90E9
59NJ => 00719C58 9090
00733C57 9090
00733ABE 90E9
@@ -642,6 +648,8 @@ Fast tekker (skips wind-up jingle)
0023EF77 jmp +0x0A
4OPU => 0023F14C mov dword [ebp + 0x14C], 1
0023F167 jmp +0x0A
5OYJ => 006D3F7B mov dword [edi + 0x14C], 1
006D3F98 jmp +0x0B
59NJ => 006DA14B mov dword [edi + 0x14C], 1
006DA168 jmp +0x0B
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)
4OPD => 002E720C 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)
0080FA58 XXXXXXXX (count 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
045D0718 40800000 // X/Z 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
+73
View File
@@ -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"
+1 -1
View File
@@ -3,7 +3,7 @@ PSOBB SUPPORT FILES, NOTES & RESOURCES
--------------------------------------------------------------------------------
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
here inside the usbb-resources folder for your convenience they are the same ones
from the old official USBB client
+340 -187
View File
@@ -1,187 +1,340 @@
0007 = Set by rico capsule in caves
000B = P2 Tyrell Start
000C = P2 Irene Start
000D = P2 Scientist 1 Start
000E = P2 Scientist 2 Start
000F = P2 More Scientist stuff.
0010 = P2 Irene after talking to Tyrell
0011 = Read a rico capsule (any)
0012 = P2 Scientist after talking to Irene.
0013 = P2 Menu 6, quest counter / Tekker talked to
0014 = Entered Forest 1
0015 = Entered Forest 2
0016 = Entered Dragon Area
0017 = Dragon defeated
0018 = Caves unlocked
0018 = P2 Principle after defeating dragon
0019 = P2 Scientist after defeating dragon
001E = Entered Caves 1 (Gov 2-1)
001F = Entered De Rol Le in 2-4
0020 = De Rol Le defeated
0021 = Mines unlocked (P2 Tyrell after defeating De Rol Le)
0028 = Entered Mines 1
0029 = Entered Vol Opt Area
002A = Defeated Vol Opt
002B = Set by rico capsule about the 3 seals (after vol opt).
002C = Activated Forest monument
002D = Activated Caves monument (Gov 2-2)
002E = Activated Mines monument
002F = Activated all monuments
0030 = Entered Ruins 1
0032 = Entered Falz 1
0035 = Hard mode unlocked
0036 = Entered Falz 3 // Very Hard mode unlocked (?)
0037 = Ultimate unlocked
0046 = One CCA door lock unlocked
0047 = One CCA door lock unlocked
0048 = One CCA door lock unlocked
0049 = Entered Laboratory
004A = Lab Assistant Start
004B = Entered Temple Beta
004C = Defeated Barba Ray
004D = Lab Assistant after defeating barba ray
004E = Entered Spaceship Beta
004F = Defeated Gol Dragon
0051 = Entered CCA
0052 = Defeated Gal Gyrphon // Defeated Gol dragon in seat of heart (?)
0054 = Entered Seabed Upper
0057 = Defeated Olga Flow
005B = Lab Natasha Start
005C = Lab Natasha after VR temple
005D = Lab Natasha after VR Spaceship
005E = Lab Assistant after defeating Gal gryphon
005F = After reading the last capsule from flowen
0060 = Lab Natasha after CCA
0065 = Cleared Magnitude of Metal
0067 = Cleared Claiming a Stake
0069 = Cleared Value of Money
006B = Cleared Battle Training
006D = Cleared Journalistic Pursuit
006F = Cleared The Fake in Yellow
0071 = Cleared Native Research
0073 = Cleared Forest of Sorrow
0075 = Cleared Gran Squall
0077 = Cleared Addicting Food
0079 = Cleared The Lost Bride
007B = Cleared Waterfall Tears
007D = Cleared Black Paper
007F = Cleared Secret Delivery
0081 = Cleared Soul of a Blacksmith
0083 = Cleared Letter from Lionel
0085 = Cleared The Grave's Butler
0087 = Cleared Knowing One's Heart
0089 = Cleared The Retired Hunter
008B = Cleared Dr. Osto's Research
008D = Cleared Unsealed Door
008F = Cleared Soul of Steel
0091 = Cleared Doc's Secret Plan (able to make enemy part weapons)
0093 = Cleared Seek my Master
0095 = Cleared From the Depths
0096 = Unknown (set in the fake in yellow)
0097 = Seat of heart unknown
009B = Cleared Central Dome Fire Swirl
00A1 = Cleared Seat of the Heart
00C9 = Got an enemy weapon converted
00CA = unknown Fake In Yellow
00CE = unknown Fake In Yellow
00D3 = Dr.Osto's research black paper subplot. Told Sue your name
00D4 = Dr.Osto's research black paper subplot. Didn't tell Sue your name from before.
00D5 = Dr.Osto's research black paper subplot. Did tell Sue your name from before.
00D6 = Unsealed door. black paper subplot Talked to Sue. Refused to tell her your name
00D7 = Unsealed door. black paper subplot. bernie tells you Sue is part of black paper.
00D8 = Black paper subplot in waterfall of tears talking to Sue
00D9 = Black paper subplot in Black paper talking to Sue (used option 2)
00DB = Black paper subplot in Black paper talking to Sue (used any option)
00DE = Black paper subplot in Black paper talked to Sue at the end of quest?
00DF = Knowing ones heart talked to Bernie?
00E0 = Seek my master. Zoke ,Donoph subplot?
00E2 = Bernie Gran Squall
00E7 = Defeated Kireek in waterfall of tears
00E8 = Black paper subplot in black paper. defeated Kireek...
00EB = Black paper subplot in from the depths. Defeated Kireek and got soul eater!
00F1 = Secret delivery. Started the Weapons subplot //is cleared if quest is left
00F3 = Weapon badge approval for claiming the snake //is cleared if quest is left
00F4 = Weapon badge approval for the lost bride //is cleared if quest is left
00F5 = Weapon badge approval for gran squall //is cleared if quest is left
00F6 = Secret delivery. Got AKIKO's FRYING PAN!
00FB = Got Orochi-agito
00FD = Unknown addicting food
0105 = Central dome fire swirl. Got Glory of the past!
0106 = Central dome fire swirl. Got Mark3.
0107 = Central dome fire swirl. got Sonic knuckles
0108 = Central dome fire swirl. got mail from BOGARDE
0109 = Central dome fire swirl. got mail from ANNA
010A = Central dome fire swirl. got mail from NADJA
010B = Central dome fire swirl. got mail from Lionel
010C = Soul of the blacksmith. Got one of the 3 special weapons!
010D = Donoph Baz dies The Retired Hunter
010E = Seat of heart unknown
010F = Seat of heart unknown
0110 = Seat of heart unknown
0111 = Seat of heart unknown
0112 = Seat of heart unknown
0113 = Seat of heart unknown
0187 = Soul of steel. Got Marina's bag! //dreamcast
0188 = Soul of steel. Unknown.
0191 = Capsule Elly VR
0197 = Cleared VR Temple
01AD = Capsule elly CCA
01AE = Capsule elly CCA
01B3 = After reading a capsule from flowen
01D6 = Set after unlocking vr spaceship
01F5 = Episode1: Cleared government 1-1
01F7 = Episode1: Cleared government 1-2
01F9 = Episode1: Cleared government 1-3
01FB = Episode1: Cleared government 2-1
01FD = Episode1: Cleared government 2-2
01FF = Episode1: Cleared government 2-3
0201 = Episode1: Cleared government 2-4
0203 = Episode1: Cleared government 3-1
0205 = Episode1: Cleared government 3-2
0207 = Episode1: Cleared government 3-3
0209 = Episode1: Cleared government 4-1
020B = Episode1: Cleared government 4-2
020D = Episode1: Cleared government 4-3
020F = Episode1: Cleared government 4-4
0211 = Episode1: Cleared government 4-5
0213 = Episode2: Cleared government 5-1 // Talked to Tekker (?)
0214 = Entered Forest 1
0215 = Episode2: Cleared government 5-2
0217 = Episode2: Cleared government 5-3 // Defeated Dragon (?)
0219 = Episode2: Cleared government 5-4
021B = Episode2: Cleared government 5-5
021D = Episode2: Cleared government 6-1
021F = Episode2: Cleared government 6-2
0220 = Defeated De Rol Le
0221 = Episode2: Cleared government 6-3
0223 = Episode2: Cleared government 6-4
0225 = Episode2: Cleared government 6-5
0227 = Episode2: Cleared government 7-1
0229 = Episode2: Cleared government 7-2
022A = Defeated Vol Opt (002A and 022A together on hard mode)
022B = Episode2: Cleared government 7-3 // Rico capsule after Vol Opt, at Ruins door (?)
022D = Episode2: Cleared government 7-4 // Entered Caves 2 (?)
022F = Episode2: Cleared government 7-5
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
0248 = Activated Seaside big door switch
024F = Defeated Gol Dragon
0252 = Defeated Gal Gryphon
02BD = Episode4: Cleared government 9-1
02BE = Episode4: Cleared government 9-2
02BF = Episode4: Cleared government 9-3
02C0 = Episode4: Cleared government 9-4
02C1 = Episode4: Cleared government 9-5
02C2 = Episode4: Cleared government 9-6
02C3 = Episode4: Cleared government 9-7
02C4 = Episode4: Cleared government 9-8
0314 = Entered Forest 1
0330 = Entered Ruins 1
03FA = P2 Menu 7, G-Counter // Talked to Momoka
03FB = Nol start
03FC = Cleared Ep2 government on ultimate
03FE = Cleared Ep2 government on normal-vh
* = verified from client or quest disassembly
0007 = Set by rico capsule in caves
000B * = Has seen first single-mode Tyrell conversation (Episode 1)
000C = P2 Irene Start
000D = P2 Scientist 1 Start
000E = P2 Scientist 2 Start
000F = P2 More Scientist stuff.
0010 = P2 Irene after talking to Tyrell
0011 = Read a rico capsule (any)
0012 = P2 Scientist after talking to Irene.
0013 = P2 Menu 6, quest counter / Tekker talked to
0014 * = Has been to Forest 1
0015 = Entered Forest 2
0016 * = TBoss1Dragon encountered
0017 * = Dragon defeated (Caves unlocked in online/multi mode (pre-V4); sets command 10 quest tier to 1)
0018 * = Talked with Tyrell after defeating Dragon (Caves unlocked in offline single mode (pre-V4))
0019 = P2 Scientist after defeating dragon
001E * = Has been to Cave 1
001F = Entered De Rol Le in 2-4
0020 * = De Rol Le defeated (Mines unlocked in online/multi mode (pre-V4); sets command 10 quest tier to 2)
0021 * = Talked with Tyrell after defeating De Rol Le (Mines unlocked in offline single mode (pre-V4))
0028 * = Has been to Mine 1
0029 * = TBoss3Volopt encountered
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).
002C * = Activated Forest monument (checked by TODoorVoShip_update and TODoorVoShip_init)
002D * = Activated Caves monument (checked by TODoorVoShip_update and TODoorVoShip_init)
002E * = Activated Mines monument (checked by TODoorVoShip_update and TODoorVoShip_init)
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 * = Has been to Ruins 1 (Ruins unlocked in offline single mode (pre-V4))
0032 * = TBoss4Type1 encountered
0033 * = TBoss4Type1 defeated; sets command 10 quest tier to 4
0034 * = TBoss4Type2 encountered
0035 * = TBoss4Type2 defeated; unlocks Hard if set in Normal
0036 * = TBoss4Type3 encountered
0037 * = TBoss4Type3 defeated; unlocks Very Hard or Ultimate if set in Hard or Very Hard respectively
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
004A = Lab Assistant Start
004B * = Has been to VR Temple Beta
004C * = Barba Ray defeated (VR Spaceship unlocked in online/multi mode (pre-V4))
004D * = Talked with Lab Assistant after defeating Barba Ray (VR Spaceship unlocked in offline single mode (pre-V4))
004E * = Has been to VR Spaceship Beta
004F * = TBoss8Dragon defeated (CCA unlocked in online/multi mode (pre-V4))
0050 * = CCA unlocked in offline single mode (pre-V4)
0051 * = Has been to CCA
0052 * = Gal Gryphon defeated (Seabed unlocked in online/multi mode (pre-V4))
0053 * = Seabed unlocked in offline single mode (pre-V4)
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
005D = Lab Natasha after VR Spaceship
005E = Lab Assistant after defeating Gal gryphon
005F = After reading the last capsule from flowen
0060 = Lab Natasha after CCA
0064 * = Magnitude of Metal unlocked
0065 * = Magnitude of Metal completed
0066 * = Claiming A Stake unlocked
0067 * = Claiming A Stake completed
0068 * = The Value of Money unlocked
0069 * = The Value of Money completed
006A * = Battle Training unlocked
006B * = Battle Training completed
006C * = Journalistic Pursuit unlocked
006D * = Journalistic Pursuit completed
006E * = The Fake in yellow unlocked
006F * = The Fake in yellow completed
0070 * = Native Research unlocked
0071 * = Native Research completed
0072 * = Forest of Sorrow unlocked
0073 * = Forest of Sorrow completed
0074 * = Gran Squall unlocked
0075 * = Gran Squall completed
0076 * = Addicting Food unlocked
0077 * = Addicting Food completed
0078 * = The Lost Bride unlocked
0079 * = The Lost Bride completed
007A * = Waterfall tears unlocked
007B * = Waterfall tears completed
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)
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
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
00CA = unknown Fake In Yellow
00CE = unknown Fake In Yellow
00D3 = Dr.Osto's research black paper subplot. Told Sue your name
00D4 = Dr.Osto's research black paper subplot. Didn't tell Sue your name from before.
00D5 = Dr.Osto's research black paper subplot. Did tell Sue your name from before.
00D6 = Unsealed door. black paper subplot Talked to Sue. Refused to tell her your name
00D7 = Unsealed door. black paper subplot. bernie tells you Sue is part of black paper.
00D8 = Black paper subplot in waterfall of tears talking to Sue
00D9 = Black paper subplot in Black paper talking to Sue (used option 2)
00DB = Black paper subplot in Black paper talking to Sue (used any option)
00DE = Black paper subplot in Black paper talked to Sue at the end of quest?
00DF = Knowing ones heart talked to Bernie?
00E0 = Seek my master. Zoke ,Donoph subplot?
00E2 = Bernie Gran Squall
00E7 = Defeated Kireek in waterfall of tears
00E8 = Black paper subplot in black paper. defeated Kireek...
00EB = Black paper subplot in from the depths. Defeated Kireek and got soul eater!
00F1 = Secret delivery. Started the Weapons subplot //is cleared if quest is left
00F3 = Weapon badge approval for claiming the snake //is cleared if quest is left
00F4 = Weapon badge approval for the lost bride //is cleared if quest is left
00F5 = Weapon badge approval for gran squall //is cleared if quest is left
00F6 = Secret delivery. Got AKIKO's FRYING PAN!
00FB = Got Orochi-agito
00FD = Unknown addicting food
0105 = Central dome fire swirl. Got Glory of the past!
0106 = Central dome fire swirl. Got Mark3.
0107 = Central dome fire swirl. got Sonic knuckles
0108 = Central dome fire swirl. got mail from BOGARDE
0109 = Central dome fire swirl. got mail from ANNA
010A = Central dome fire swirl. got mail from NADJA
010B = Central dome fire swirl. got mail from Lionel
010C = Soul of the blacksmith. Got one of the 3 special weapons!
010D = Donoph Baz dies The Retired Hunter
010E = Seat of heart unknown
010F = Seat of heart unknown
0110 = Seat of heart unknown
0111 = Seat of heart unknown
0112 = Seat of heart unknown
0113 = Seat of heart unknown
0187 = Soul of steel. Got Marina's bag! //dreamcast
0188 = Soul of steel. Unknown.
0191 = Capsule Elly VR
0197 = Cleared VR Temple
01AD = Capsule elly CCA
01AE = Capsule elly CCA
01B3 = After reading a capsule from flowen
01D6 = Set after unlocking vr spaceship
01F5 * = Episode1: Cleared government 1-1
01F7 * = Episode1: Cleared government 1-2
01F9 * = Episode1: Cleared government 1-3; Caves unlocked (V4)
01FB * = Episode1: Cleared government 2-1
01FD * = Episode1: Cleared government 2-2
01FF * = Episode1: Cleared government 2-3
0201 * = Episode1: Cleared government 2-4; Mines unlocked (V4)
0203 * = Episode1: Cleared government 3-1
0205 * = Episode1: Cleared government 3-2
0207 * = Episode1: Cleared government 3-3; Ruins unlocked (V4)
0209 * = Episode1: Cleared government 4-1
020B * = Episode1: Cleared government 4-2
020D * = Episode1: Cleared government 4-3
020F * = Episode1: Cleared government 4-4
0211 * = Episode1: Cleared government 4-5
0213 * = Episode2: Cleared government 5-1
0215 * = Episode2: Cleared government 5-2
0217 * = Episode2: Cleared government 5-3
0219 * = Episode2: Cleared government 5-4
021B * = Episode2: Cleared government 5-5; VR Spaceship unlocked (V4)
021D * = Episode2: Cleared government 6-1
021F * = Episode2: Cleared government 6-2
0221 * = Episode2: Cleared government 6-3
0223 * = Episode2: Cleared government 6-4
0225 * = Episode2: Cleared government 6-5; CCA unlocked (V4)
0227 * = Episode2: Cleared government 7-1
0229 * = Episode2: Cleared government 7-2
022B * = Episode2: Cleared government 7-3
022D * = Episode2: Cleared government 7-4
022F * = Episode2: Cleared government 7-5; Seabed unlocked (V4)
0231 * = Episode2: Cleared government 8-1
0233 * = Episode2: Cleared government 8-2
0235 * = Episode2: Cleared government 8-3
0246 = Activated Jungle East big door switch
0248 = Activated Seaside big door switch
024F = Defeated Gol Dragon
0252 = Defeated Gal Gryphon
02BD * = Episode4: Cleared government 9-1; Crater West unlocked
02BE * = Episode4: Cleared government 9-2; Crater South unlocked
02BF * = Episode4: Cleared government 9-3; Crater North unlocked
02C0 * = Episode4: Cleared government 9-4; Crater Interior unlocked
02C1 * = Episode4: Cleared government 9-5; Desert unlocked
02C2 * = Episode4: Cleared government 9-6
02C3 * = Episode4: Cleared government 9-7
02C4 * = Episode4: Cleared government 9-8
0314 = Entered Forest 1
0330 = Entered Ruins 1
03FA = P2 Menu 7, G-Counter // Talked to Momoka
03FB = Nol start
03FC = Cleared Ep2 government on ultimate
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
-27
View File
@@ -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
View File
@@ -9,10 +9,7 @@
#include "Text.hh"
using namespace std;
AFSArchive::AFSArchive(shared_ptr<const string> data)
: data(data) {
AFSArchive::AFSArchive(std::shared_ptr<const std::string> data) : data(data) {
struct FileHeader {
be_uint32_t magic;
le_uint32_t num_files;
@@ -26,7 +23,7 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
phosg::StringReader r(*this->data);
const auto& header = r.get<FileHeader>();
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) {
@@ -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);
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()) {
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);
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 {
@@ -57,12 +54,12 @@ phosg::StringReader AFSArchive::get_reader(size_t index) const {
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);
}
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;
w.put_u32b(0x41465300); // 'AFS\0'
w.put<U32T<BE>>(files.size());
+189 -190
View File
@@ -10,23 +10,21 @@
#include "Account.hh"
using namespace std;
shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
auto ret = make_shared<DCNTELicense>();
std::shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
auto ret = std::make_shared<DCNTELicense>();
ret->serial_number = json.get_string("SerialNumber");
ret->access_key = json.get_string("AccessKey");
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()) {
throw runtime_error("serial number is too short");
throw std::runtime_error("serial number is too short");
}
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()) {
throw runtime_error("access key is too short");
throw std::runtime_error("access key is too short");
}
return ret;
}
@@ -35,15 +33,15 @@ phosg::JSON DCNTELicense::json() const {
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
}
shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
auto ret = make_shared<V1V2License>();
std::shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
auto ret = std::make_shared<V1V2License>();
ret->serial_number = json.get_int("SerialNumber");
ret->access_key = json.get_string("AccessKey");
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) {
throw runtime_error("access key length is incorrect");
throw std::runtime_error("access key length is incorrect");
}
return ret;
}
@@ -52,19 +50,19 @@ phosg::JSON V1V2License::json() const {
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
}
shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
auto ret = make_shared<GCLicense>();
std::shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
auto ret = std::make_shared<GCLicense>();
ret->serial_number = json.get_int("SerialNumber");
ret->access_key = json.get_string("AccessKey");
ret->password = json.get_string("Password");
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) {
throw runtime_error("access key length is incorrect");
throw std::runtime_error("access key length is incorrect");
}
if (ret->password.empty()) {
throw runtime_error("password is too short");
throw std::runtime_error("password is too short");
}
return ret;
}
@@ -77,19 +75,19 @@ phosg::JSON GCLicense::json() const {
});
}
shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
auto ret = make_shared<XBLicense>();
std::shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
auto ret = std::make_shared<XBLicense>();
ret->gamertag = json.get_string("GamerTag");
ret->user_id = json.get_int("UserID");
ret->account_id = json.get_int("AccountID");
if (ret->gamertag.empty()) {
throw runtime_error("gamertag is too short");
throw std::runtime_error("gamertag is too short");
}
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) {
throw runtime_error("account ID is zero");
throw std::runtime_error("account ID is zero");
}
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}});
}
shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
auto ret = make_shared<BBLicense>();
std::shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
auto ret = std::make_shared<BBLicense>();
ret->username = json.get_string("UserName");
ret->password = json.get_string("Password");
if (ret->username.size() > 16) {
throw runtime_error("username is too long");
throw std::runtime_error("username is too long");
}
if (ret->username.empty()) {
throw runtime_error("username is too short");
throw std::runtime_error("username is too short");
}
if (ret->password.size() > 16) {
throw runtime_error("password is too long");
throw std::runtime_error("password is too long");
}
if (ret->password.empty()) {
throw runtime_error("password is too short");
throw std::runtime_error("password is too short");
}
return ret;
}
@@ -132,51 +130,51 @@ Account::Account(const phosg::JSON& json)
uint64_t format_version = 0;
try {
format_version = json.get_int("FormatVersion");
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (format_version == 0) {
// Original format - no account ID
this->account_id = json.get_int("SerialNumber");
string access_key = json.get_string("AccessKey", "");
string dc_nte_serial_number = json.get_string("DCNTESerialNumber", "");
string dc_nte_access_key = json.get_string("DCNTEAccessKey", "");
string gc_password = json.get_string("GCPassword", "");
string xb_gamertag = json.get_string("XBGamerTag", "");
std::string access_key = json.get_string("AccessKey", "");
std::string dc_nte_serial_number = json.get_string("DCNTESerialNumber", "");
std::string dc_nte_access_key = json.get_string("DCNTEAccessKey", "");
std::string gc_password = json.get_string("GCPassword", "");
std::string xb_gamertag = json.get_string("XBGamerTag", "");
uint64_t xb_user_id = json.get_int("XBUserID", 0);
uint64_t xb_account_id = json.get_int("XBAccountID", 0);
string bb_username = json.get_string("BBUsername", "");
string bb_password = json.get_string("BBPassword", "");
std::string bb_username = json.get_string("BBUsername", "");
std::string bb_password = json.get_string("BBPassword", "");
if (access_key.size() == 12) {
if (!gc_password.empty()) {
auto lic = make_shared<GCLicense>();
auto lic = std::make_shared<GCLicense>();
lic->serial_number = this->account_id;
lic->access_key = access_key;
lic->password = gc_password;
this->gc_licenses.emplace(lic->serial_number, lic);
}
} else if (access_key.size() >= 8) {
auto lic = make_shared<V1V2License>();
auto lic = std::make_shared<V1V2License>();
lic->serial_number = this->account_id;
lic->access_key = access_key.substr(0, 8);
this->dc_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()) {
auto lic = make_shared<DCNTELicense>();
auto lic = std::make_shared<DCNTELicense>();
lic->serial_number = dc_nte_serial_number;
lic->access_key = dc_nte_access_key;
this->dc_nte_licenses.emplace(lic->serial_number, lic);
}
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->user_id = xb_user_id;
lic->account_id = xb_account_id;
this->xb_licenses.emplace(lic->user_id, lic);
}
if (!bb_username.empty() && !bb_password.empty()) {
auto lic = make_shared<BBLicense>();
auto lic = std::make_shared<BBLicense>();
lic->username = bb_username;
lic->password = bb_password;
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")) {
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);
if (this->flags) {
string flags_str = "";
std::string flags_str = "";
if (this->flags == static_cast<uint32_t>(Flag::ROOT)) {
flags_str = "ROOT";
} else if (this->flags == static_cast<uint32_t>(Flag::ADMINISTRATOR)) {
@@ -334,7 +332,7 @@ string Account::str() const {
}
if (this->user_flags) {
string user_flags_str = "";
std::string user_flags_str = "";
if (this->check_user_flag(UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST)) {
user_flags_str += "DISABLE_DROP_NOTIFICATION_BROADCAST,";
}
@@ -347,8 +345,7 @@ string Account::str() const {
}
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, time_str);
ret += std::format(" Banned until: {} ({})\n", this->ban_end_time, phosg::format_time(this->ban_end_time));
}
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
ret += std::format(" Episode 3 meseta: {} (total earned: {})\n",
@@ -399,20 +396,19 @@ string Account::str() const {
void Account::save() const {
if (!this->is_temporary) {
auto json = this->json();
string json_data = json.serialize(
std::string json_data = json.serialize(
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);
}
}
void Account::delete_file() const {
string filename = std::format("system/licenses/{:010}.json", this->account_id);
remove(filename.c_str());
std::filesystem::remove(std::format("system/licenses/{:010}.json", this->account_id));
}
string Login::str() const {
string ret = std::format("Account:{:08X}", this->account->account_id);
std::string Login::str() const {
std::string ret = std::format("Account:{:08X}", this->account->account_id);
if (this->account_was_created) {
ret += " (new)";
}
@@ -435,21 +431,21 @@ string Login::str() const {
}
size_t AccountIndex::count() const {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
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 {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
return this->by_account_id.at(account_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
throw missing_account();
}
}
shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const string& serial_number, const string& access_key) {
auto login = make_shared<Login>();
std::shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const std::string& serial_number, const std::string& access_key) {
auto login = std::make_shared<Login>();
login->account = this->by_dc_nte_serial_number.at(serial_number);
login->dc_nte_license = login->account->dc_nte_licenses.at(serial_number);
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;
}
shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
const string& serial_number, const string& access_key, bool allow_create) {
std::shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
const std::string& serial_number, const std::string& access_key, bool allow_create) {
if (serial_number.empty()) {
throw no_username();
}
try {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
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 {
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) {
auto login = make_shared<Login>();
auto login = std::make_shared<Login>();
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;
auto lic = make_shared<DCNTELicense>();
auto lic = std::make_shared<DCNTELicense>();
lic->serial_number = serial_number;
lic->access_key = access_key;
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(
uint32_t serial_number, const string& access_key, const string& character_name) {
auto login = make_shared<Login>();
std::shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
uint32_t serial_number, const std::string& access_key, const std::string& character_name) {
auto login = std::make_shared<Login>();
login->account = this->by_dc_serial_number.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);
@@ -514,30 +510,30 @@ shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
return login;
}
shared_ptr<Login> AccountIndex::from_dc_credentials(
uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) {
std::shared_ptr<Login> AccountIndex::from_dc_credentials(
uint32_t serial_number, const std::string& access_key, const std::string& character_name, bool allow_create) {
if (serial_number == 0) {
throw no_username();
}
try {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
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 {
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) {
auto login = make_shared<Login>();
auto login = std::make_shared<Login>();
login->account_was_created = true;
login->account = make_shared<Account>();
login->account = std::make_shared<Account>();
login->account->account_id = serial_number;
auto lic = make_shared<V1V2License>();
auto lic = std::make_shared<V1V2License>();
lic->serial_number = serial_number;
lic->access_key = access_key;
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) {
throw missing_account();
}
if (guild_card_number == 0xFFFFFFFF) {
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 = make_shared<Account>();
login->account = std::make_shared<Account>();
login->account->account_id = guild_card_number;
login->account->is_temporary = true;
auto lic = make_shared<V1V2License>();
auto lic = std::make_shared<V1V2License>();
lic->serial_number = guild_card_number;
login->account->pc_licenses.emplace(lic->serial_number, lic);
login->pc_license = lic;
@@ -569,9 +565,9 @@ shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_numb
return login;
}
shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
uint32_t serial_number, const string& access_key, const string& character_name) {
auto login = make_shared<Login>();
std::shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
uint32_t serial_number, const std::string& access_key, const std::string& character_name) {
auto login = std::make_shared<Login>();
login->account = this->by_pc_serial_number.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);
@@ -587,30 +583,30 @@ shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
return login;
}
shared_ptr<Login> AccountIndex::from_pc_credentials(
uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) {
std::shared_ptr<Login> AccountIndex::from_pc_credentials(
uint32_t serial_number, const std::string& access_key, const std::string& character_name, bool allow_create) {
if (serial_number == 0) {
throw no_username();
}
try {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
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 {
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) {
auto login = make_shared<Login>();
auto login = std::make_shared<Login>();
login->account_was_created = true;
login->account = make_shared<Account>();
login->account = std::make_shared<Account>();
login->account->account_id = serial_number;
auto lic = make_shared<V1V2License>();
auto lic = std::make_shared<V1V2License>();
lic->serial_number = serial_number;
lic->access_key = access_key;
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(
uint32_t serial_number, const string& access_key, const string* password, const string& character_name) {
auto login = make_shared<Login>();
std::shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
uint32_t serial_number,
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->gc_license = login->account->gc_licenses.at(serial_number);
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;
}
shared_ptr<Login> AccountIndex::from_gc_credentials(
std::shared_ptr<Login> AccountIndex::from_gc_credentials(
uint32_t serial_number,
const string& access_key,
const string* password,
const string& character_name,
const std::string& access_key,
const std::string* password,
const std::string& character_name,
bool allow_create) {
if (serial_number == 0) {
throw no_username();
}
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);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
unique_lock g(this->lock);
std::unique_lock g(this->lock);
try {
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) {
auto login = make_shared<Login>();
auto login = std::make_shared<Login>();
login->account_was_created = true;
login->account = make_shared<Account>();
login->account = std::make_shared<Account>();
login->account->account_id = serial_number;
auto lic = make_shared<GCLicense>();
auto lic = std::make_shared<GCLicense>();
lic->serial_number = serial_number;
lic->access_key = access_key;
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) {
auto login = make_shared<Login>();
std::shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
auto login = std::make_shared<Login>();
login->account = this->by_xb_user_id.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())) {
@@ -693,30 +692,30 @@ shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
return login;
}
shared_ptr<Login> AccountIndex::from_xb_credentials(
const string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) {
std::shared_ptr<Login> AccountIndex::from_xb_credentials(
const std::string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) {
if (user_id == 0 || account_id == 0) {
throw incorrect_access_key();
}
try {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
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 {
return this->from_xb_credentials_locked(user_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (allow_create) {
auto login = make_shared<Login>();
auto login = std::make_shared<Login>();
login->account_was_created = true;
login->account = make_shared<Account>();
login->account = std::make_shared<Account>();
login->account->account_id = phosg::fnv1a32(gamertag) & 0x7FFFFFFF;
auto lic = make_shared<XBLicense>();
auto lic = std::make_shared<XBLicense>();
lic->gamertag = gamertag;
lic->user_id = user_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) {
auto login = make_shared<Login>();
std::shared_ptr<Login> AccountIndex::from_bb_credentials_locked(
const std::string& username, const std::string* password) {
auto login = std::make_shared<Login>();
login->account = this->by_bb_username.at(username);
login->bb_license = login->account->bb_licenses.at(username);
if (password && (login->bb_license->password != *password)) {
@@ -742,30 +742,30 @@ shared_ptr<Login> AccountIndex::from_bb_credentials_locked(const string& usernam
return login;
}
shared_ptr<Login> AccountIndex::from_bb_credentials(
const string& username, const string* password, bool allow_create) {
std::shared_ptr<Login> AccountIndex::from_bb_credentials(
const std::string& username, const std::string* password, bool allow_create) {
if (username.empty() || (password && password->empty())) {
throw no_username();
}
try {
shared_lock g(this->lock);
std::shared_lock g(this->lock);
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 {
return this->from_bb_credentials_locked(username, password);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (allow_create && password) {
auto login = make_shared<Login>();
auto login = std::make_shared<Login>();
login->account_was_created = true;
login->account = make_shared<Account>();
login->account = std::make_shared<Account>();
login->account->account_id = phosg::fnv1a32(username) & 0x7FFFFFFF;
auto lic = make_shared<BBLicense>();
auto lic = std::make_shared<BBLicense>();
lic->username = username;
lic->password = *password;
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 {
shared_lock g(this->lock);
vector<shared_ptr<Account>> ret;
std::vector<std::shared_ptr<Account>> AccountIndex::all() const {
std::shared_lock g(this->lock);
std::vector<std::shared_ptr<Account>> ret;
ret.reserve(this->by_account_id.size());
for (const auto& it : this->by_account_id) {
ret.emplace_back(it.second);
@@ -787,44 +787,44 @@ vector<shared_ptr<Account>> AccountIndex::all() const {
return ret;
}
void AccountIndex::add(shared_ptr<Account> a) {
unique_lock g(this->lock);
void AccountIndex::add(std::shared_ptr<Account> a) {
std::unique_lock g(this->lock);
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) {
a->is_temporary = true;
}
for (const auto& it : a->dc_nte_licenses) {
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) {
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) {
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) {
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) {
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) {
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) {
unique_lock g(this->lock);
std::unique_lock g(this->lock);
auto acc_it = this->by_account_id.find(account_id);
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);
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
throw runtime_error("username already registered");
throw std::runtime_error("username already registered");
}
if (!account->bb_licenses.emplace(license->username, license).second) {
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);
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)) {
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);
}
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);
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)) {
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);
}
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);
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)) {
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);
}
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);
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)) {
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);
}
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);
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)) {
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);
}
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);
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)) {
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);
}
shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
shared_ptr<const Account> src_a, const string& variation_data) const {
auto ret = make_shared<Account>(*src_a);
std::shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
std::shared_ptr<const Account> src_a, const std::string& variation_data) const {
auto ret = std::make_shared<Account>(*src_a);
ret->is_temporary = true;
ret->account_id = phosg::fnv1a32(&src_a->account_id, sizeof(src_a->account_id));
ret->account_id = phosg::fnv1a32(variation_data, ret->account_id);
return ret;
}
AccountIndex::AccountIndex(bool force_all_temporary)
: force_all_temporary(force_all_temporary) {
AccountIndex::AccountIndex(bool force_all_temporary) : force_all_temporary(force_all_temporary) {
if (!this->force_all_temporary) {
if (!std::filesystem::is_directory("system/licenses")) {
std::filesystem::create_directories("system/licenses");
} else {
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")) {
try {
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + filename));
this->add(make_shared<Account>(json));
} catch (const exception& e) {
this->add(std::make_shared<Account>(json));
} catch (const std::exception& e) {
phosg::log_error_f("Failed to index account {}", filename);
throw;
}
+104 -106
View File
@@ -14,8 +14,6 @@
#include "Text.hh"
#include "Types.hh"
using namespace std;
class AddressTranslator {
public:
enum class ExpandMethod {
@@ -63,7 +61,7 @@ public:
case ExpandMethod::RAW_BOTH:
return "RAW_BOTH";
default:
throw logic_error("invalid expand method");
throw std::logic_error("invalid expand method");
}
}
@@ -85,7 +83,7 @@ public:
case ExpandMethod::RAW_BOTH:
return false;
default:
throw logic_error("invalid expand method");
throw std::logic_error("invalid expand method");
}
}
@@ -107,46 +105,46 @@ public:
case ExpandMethod::RAW_BOTH:
return false;
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] "),
directory(directory) {
while (this->directory.ends_with("/")) {
this->directory.pop_back();
}
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) {
continue;
}
string name = filename.substr(0, filename.size() - 4);
string path = directory + "/" + filename;
std::string name = filename.substr(0, filename.size() - 4);
std::string path = directory + "/" + filename;
if (filename.ends_with(".dol")) {
ResourceDASM::DOLFile dol(path.c_str());
auto mem = make_shared<ResourceDASM::MemoryContext>();
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
dol.load_into(mem);
this->mems.emplace(name, mem);
this->ppc_mems.emplace(mem);
this->log.info_f("Loaded {}", name);
} else if (filename.ends_with(".xbe")) {
ResourceDASM::XBEFile xbe(path.c_str());
auto mem = make_shared<ResourceDASM::MemoryContext>();
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
xbe.load_into(mem);
this->mems.emplace(name, mem);
this->log.info_f("Loaded {}", name);
} else if (filename.ends_with(".exe")) {
ResourceDASM::PEFile pe(path.c_str());
auto mem = make_shared<ResourceDASM::MemoryContext>();
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
pe.load_into(mem);
this->mems.emplace(name, mem);
this->log.info_f("Loaded {}", name);
} else if (filename.ends_with(".bin")) {
string data = phosg::load_file(path);
auto mem = make_shared<ResourceDASM::MemoryContext>();
std::string data = phosg::load_file(path);
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
mem->allocate_at(0x8C010000, data.size());
mem->memcpy(0x8C010000, data.data(), data.size());
this->mems.emplace(name, mem);
@@ -156,10 +154,10 @@ public:
}
~AddressTranslator() = default;
const string& get_source_filename() const {
const std::string& get_source_filename() const {
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_mem = this->mems.at(this->src_filename);
}
@@ -178,25 +176,25 @@ public:
uint32_t opcode = r.get_u32b();
if ((opcode & 0xFFFF0000) == 0x3DA00000) {
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 |= (opcode << 16);
} else if ((opcode & 0xFFFF0000) == 0x3C400000) {
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 |= (opcode << 16);
} else if ((opcode & 0xFFFF0000) == 0x61AD0000) {
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 |= (opcode & 0xFFFF);
} else if ((opcode & 0xFFFF0000) == 0x60420000) {
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 |= (opcode & 0xFFFF);
@@ -217,11 +215,11 @@ public:
}
struct ParseDATConstructorTableSpec {
string src_name;
std::string src_name;
uint32_t index_addr;
size_t num_areas;
bool has_names;
vector<uint32_t> x86_constructor_calls;
std::vector<uint32_t> x86_constructor_calls;
ParseDATConstructorTableSpec(const phosg::JSON& json) {
this->src_name = json.at("SourceName").as_string();
@@ -233,8 +231,8 @@ public:
}
}
static vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
vector<ParseDATConstructorTableSpec> ret;
static std::vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
std::vector<ParseDATConstructorTableSpec> ret;
for (const auto& z : json.as_list()) {
ret.emplace_back(*z);
}
@@ -267,25 +265,25 @@ public:
// Returns {type: {constructor_addr: [(start_area, end_area), ...]}}
template <typename EntryT>
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> parse_dat_constructor_table_t(
shared_ptr<const ResourceDASM::MemoryContext>& mem, const ParseDATConstructorTableSpec& spec) {
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> parse_dat_constructor_table_t(
std::shared_ptr<const ResourceDASM::MemoryContext>& mem, const ParseDATConstructorTableSpec& spec) {
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
// 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
// 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()) {
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;
for (uint32_t constructor_addr : spec.x86_constructor_calls) {
ResourceDASM::X86Emulator emu(constructed_mem);
// Uncomment for debugging
// auto debugger = make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
// auto debugger = std::make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
// debugger->bind(emu);
// debugger->state.mode = ResourceDASM::DebuggerMode::TRACE;
@@ -295,7 +293,7 @@ public:
constructed_mem->write_u32l(esp - 4, 0xFFFFFFFF); // Return addr
try {
emu.execute();
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
if (regs.eip != 0xFFFFFFFF) {
throw;
}
@@ -304,7 +302,7 @@ public:
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));
for (size_t area = 0; area < spec.num_areas; area++) {
@@ -322,18 +320,18 @@ public:
if (!group.empty() && (group.back().second == (area - 1))) {
group.back().second = area;
} else {
group.emplace_back(make_pair(area, area));
group.emplace_back(std::make_pair(area, area));
}
}
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;
}
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;
for (const auto& [start, end] : ranges) {
for (size_t z = start; z <= end; z++) {
@@ -344,7 +342,7 @@ public:
}
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);
if (this->ppc_mems.count(spec_mem)) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
@@ -374,10 +372,10 @@ public:
}
void parse_dat_constructor_table_multi(
const 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;
const std::vector<ParseDATConstructorTableSpec>& specs, bool is_enemies, bool print_area_masks) {
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) {
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);
if (this->ppc_mems.count(spec_mem)) {
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));
}
map<string, size_t> version_widths;
map<uint32_t, map<string, string>> formatted_cells_for_type;
std::map<std::string, size_t> version_widths;
std::map<uint32_t, std::map<std::string, std::string>> formatted_cells_for_type;
for (const auto& spec : specs) {
const auto& table = all_tables.at(spec.src_name);
size_t max_width = 0;
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) {
if (!cell_data.empty()) {
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);
}
version_widths[spec.src_name] = max_width;
}
vector<string> formatted_lines;
string header_line = "TYPE =>";
std::vector<std::string> formatted_lines;
std::string header_line = "TYPE =>";
for (const auto& spec : specs) {
size_t width = version_widths.at(spec.src_name);
header_line.push_back(' ');
@@ -436,7 +434,7 @@ public:
header_line += " NAME";
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) {
size_t width = version_widths.at(spec.src_name);
try {
@@ -446,7 +444,7 @@ public:
if (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), ' ');
}
}
@@ -466,21 +464,21 @@ public:
}
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_size,
ExpandMethod expand_method) const {
bool is_ppc = this->is_ppc_expand_method(expand_method);
bool is_ppc_data = this->is_ppc_data_expand_method(expand_method);
if (!this->src_mem) {
throw runtime_error("no source file selected");
throw std::runtime_error("no source file selected");
}
if (src_size == 0) {
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()) {
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
src_section = sec;
@@ -488,7 +486,7 @@ public:
}
}
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);
@@ -570,7 +568,7 @@ public:
if (num_matches == 1) {
return last_match_address;
} 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_forward = false;
@@ -614,10 +612,10 @@ public:
can_expand_forward = (src_bytes_available_after > match_bytes_after);
break;
default:
throw logic_error("invalid expand method");
throw std::logic_error("invalid expand method");
}
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) {
match_bytes_before += (is_ppc ? 4 : 1);
@@ -626,7 +624,7 @@ public:
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 {
@@ -637,18 +635,18 @@ public:
void find_all_matches(uint32_t src_addr, uint32_t src_size, MatchType type) const {
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) {
if (it.second == this->src_mem) {
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
results.emplace(it.first, src_addr);
} else {
vector<future<uint32_t>> futures;
static const vector<ExpandMethod> ppc_methods = {
std::vector<std::future<uint32_t>> futures;
static const std::vector<ExpandMethod> ppc_methods = {
ExpandMethod::PPC_TEXT_FORWARD,
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
ExpandMethod::PPC_TEXT_BACKWARD,
@@ -660,7 +658,7 @@ public:
ExpandMethod::PPC_DATA_BACKWARD,
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_WITH_BARRIER,
ExpandMethod::PPC_TEXT_BACKWARD,
@@ -669,18 +667,18 @@ public:
ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER,
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_BACKWARD,
ExpandMethod::PPC_DATA_BOTH,
};
static const vector<ExpandMethod> raw_methods = {
static const std::vector<ExpandMethod> raw_methods = {
ExpandMethod::RAW_FORWARD,
ExpandMethod::RAW_BACKWARD,
ExpandMethod::RAW_BOTH,
};
const vector<ExpandMethod>* methods;
const std::vector<ExpandMethod>* methods;
if (this->ppc_mems.count(it.second)) {
if (type == MatchType::ANY) {
methods = &ppc_methods;
@@ -689,7 +687,7 @@ public:
} else if (type == MatchType::DATA) {
methods = &ppc_data_methods;
} else {
throw logic_error("invalid match type");
throw std::logic_error("invalid match type");
}
} else {
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)));
}
unordered_set<uint32_t> match_addrs;
std::unordered_set<uint32_t> match_addrs;
for (size_t z = 0; z < futures.size(); z++) {
const char* method_name = this->name_for_expand_method(methods->at(z));
try {
uint32_t ret = futures[z].get();
log.info_f("({}) ({}) {:08X}", it.first, method_name, ret);
match_addrs.emplace(ret);
} catch (const exception& e) {
} catch (const std::exception& e) {
log.error_f("({}) ({}) failed: {}", it.first, method_name, e.what());
}
}
@@ -726,12 +724,12 @@ public:
}
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) {
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()) {
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
src_section = sec;
@@ -739,7 +737,7 @@ public:
}
}
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;
@@ -782,12 +780,12 @@ public:
if (num_matches == 1) {
return last_match_address;
} 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_forward = (src_bytes_available_after >= match_bytes_after + 4);
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) {
match_bytes_before += 4;
@@ -796,15 +794,15 @@ public:
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 {
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) {
if (it.second == this->src_mem) {
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
@@ -815,7 +813,7 @@ public:
try {
ret = this->find_be_to_le_data_match(it.second, src_addr, src_size);
log.info_f("({}) {:08X}", it.first, ret);
} catch (const exception& e) {
} catch (const std::exception& e) {
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& [sec_addr, sec_size] : mem->allocated_blocks()) {
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, ' ');
if (tokens.empty()) {
throw runtime_error("no command given");
throw std::runtime_error("no command given");
}
phosg::strip_trailing_whitespace(tokens[tokens.size() - 1]);
@@ -856,7 +854,7 @@ public:
} else if (tokens[0] == "find") {
this->find_data(phosg::parse_data_string(tokens.at(1)));
} 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();) {
if (to_keep.count(it->first)) {
it++;
@@ -891,7 +889,7 @@ public:
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);
} else if (!tokens[0].empty()) {
throw runtime_error("unknown command");
throw std::runtime_error("unknown command");
}
}
@@ -904,10 +902,10 @@ public:
}
fflush(stdout);
string command = phosg::fgets(stdin);
std::string command = phosg::fgets(stdin);
try {
this->handle_command(command);
} catch (const exception& e) {
} catch (const std::exception& e) {
this->log.error_f("Failed: {}", e.what());
}
}
@@ -916,14 +914,14 @@ public:
private:
phosg::PrefixedLogger log;
string directory;
unordered_map<string, shared_ptr<const ResourceDASM::MemoryContext>> mems;
unordered_set<shared_ptr<const ResourceDASM::MemoryContext>> ppc_mems;
string src_filename;
shared_ptr<const ResourceDASM::MemoryContext> src_mem;
std::string directory;
std::unordered_map<std::string, std::shared_ptr<const ResourceDASM::MemoryContext>> mems;
std::unordered_set<std::shared_ptr<const ResourceDASM::MemoryContext>> ppc_mems;
std::string src_filename;
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);
if (!use_filename.empty()) {
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 b(b_filename.c_str());
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
auto a_mem = std::make_shared<ResourceDASM::MemoryContext>();
auto b_mem = std::make_shared<ResourceDASM::MemoryContext>();
a.load_into(a_mem);
b.load_into(b_mem);
uint32_t min_addr = 0xFFFFFFFF;
uint32_t max_addr = 0x00000000;
for (const auto& sec : a.sections) {
min_addr = min<uint32_t>(min_addr, sec.address);
max_addr = max<uint32_t>(max_addr, sec.address + sec.data.size());
min_addr = std::min<uint32_t>(min_addr, sec.address);
max_addr = std::max<uint32_t>(max_addr, sec.address + sec.data.size());
}
for (const auto& sec : b.sections) {
min_addr = min<uint32_t>(min_addr, sec.address);
max_addr = max<uint32_t>(max_addr, sec.address + sec.data.size());
min_addr = std::min<uint32_t>(min_addr, sec.address);
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) {
bool a_exists = a_mem->exists(addr, 4);
bool b_exists = b_mem->exists(addr, 4);
if (a_exists && b_exists) {
string a_value = a_mem->read(addr, 4);
string b_value = b_mem->read(addr, 4);
std::string a_value = a_mem->read(addr, 4);
std::string b_value = b_mem->read(addr, 4);
if (a_value != b_value) {
if (!ret.empty() && (ret.back().address + ret.back().b_data.size() == addr)) {
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;
}
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 b(b_filename.c_str());
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
auto a_mem = std::make_shared<ResourceDASM::MemoryContext>();
auto b_mem = std::make_shared<ResourceDASM::MemoryContext>();
a.load_into(a_mem);
b.load_into(b_mem);
uint32_t min_addr = 0xFFFFFFFF;
uint32_t max_addr = 0x00000000;
for (const auto& sec : a.sections) {
min_addr = min<uint32_t>(min_addr, sec.addr);
max_addr = max<uint32_t>(max_addr, sec.addr + sec.size);
min_addr = std::min<uint32_t>(min_addr, sec.addr);
max_addr = std::max<uint32_t>(max_addr, sec.addr + sec.size);
}
for (const auto& sec : b.sections) {
min_addr = min<uint32_t>(min_addr, sec.addr);
max_addr = max<uint32_t>(max_addr, sec.addr + sec.size);
min_addr = std::min<uint32_t>(min_addr, sec.addr);
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++) {
bool a_exists = a_mem->exists(addr, 1);
bool b_exists = b_mem->exists(addr, 1);
+19 -21
View File
@@ -14,9 +14,7 @@
#include "Revision.hh"
#include "Server.hh"
using namespace std;
static const unordered_map<int, const char*> explanation_for_response_code{
static const std::unordered_map<int, const char*> explanation_for_response_code{
{100, "Continue"},
{101, "Switching Protocols"},
{102, "Processing"},
@@ -92,7 +90,7 @@ const std::string* HTTPRequest::get_header(const std::string& name) const {
if (its.first == its.second) {
return nullptr;
}
const string* ret = &its.first->second;
const std::string* ret = &its.first->second;
its.first++;
if (its.first != its.second) {
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) {
return nullptr;
}
const string* ret = &its.first->second;
const std::string* ret = &its.first->second;
its.first++;
if (its.first != its.second) {
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;
}
static void url_decode_inplace(string& s) {
static void url_decode_inplace(std::string& s) {
size_t write_offset = 0, read_offset = 0;
for (; read_offset < s.size(); write_offset++) {
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);
auto line_tokens = phosg::split(request_line, ' ');
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];
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]);
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);
line_tokens[1].resize(fragment_start_offset);
}
size_t query_start_offset = line_tokens[1].find('?');
string query;
if (query_start_offset != string::npos) {
std::string query;
if (query_start_offset != std::string::npos) {
query = line_tokens[1].substr(query_start_offset + 1);
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, '&');
for (auto& token : query_tokens) {
size_t equals_pos = token.find('=');
if (equals_pos == string::npos) {
if (equals_pos == std::string::npos) {
url_decode_inplace(token);
req.query_params.emplace(std::move(token), "");
} else {
string key = token.substr(0, equals_pos);
string value = token.substr(equals_pos + 1);
std::string key = token.substr(0, equals_pos);
std::string value = token.substr(equals_pos + 1);
url_decode_inplace(key);
url_decode_inplace(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 {
size_t colon_pos = line.find(':');
if (colon_pos == string::npos) {
throw runtime_error("malformed header line");
if (colon_pos == std::string::npos) {
throw std::runtime_error("malformed header line");
}
string key = line.substr(0, colon_pos);
string value = line.substr(colon_pos + 1);
std::string key = line.substr(0, colon_pos);
std::string value = line.substr(colon_pos + 1);
phosg::strip_whitespace(key);
phosg::strip_whitespace(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");
if (transfer_encoding_header && phosg::tolower(*transfer_encoding_header) == "chunked") {
deque<string> chunks;
std::deque<std::string> chunks;
size_t total_data_bytes = 0;
for (;;) {
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) {
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
@@ -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) {
@@ -396,7 +394,7 @@ asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, 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);
}
+19 -21
View File
@@ -7,15 +7,12 @@
#include <phosg/Strings.hh>
#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() {
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->waiters.swap(waiters_to_resume);
}
@@ -28,7 +25,7 @@ void AsyncEvent::set() {
}
void AsyncEvent::clear() {
lock_guard g(this->lock);
std::lock_guard g(this->lock);
this->is_set = false;
}
@@ -36,11 +33,12 @@ asio::awaitable<void> AsyncEvent::wait() {
auto token = asio::use_awaitable_t<>{};
co_await asio::async_initiate<asio::use_awaitable_t<>, void()>(
[this](auto&& handler) -> void {
lock_guard g(this->lock);
std::lock_guard g(this->lock);
if (this->is_set) {
handler();
} 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);
@@ -49,17 +47,17 @@ asio::awaitable<void> AsyncEvent::wait() {
AsyncSocketReader::AsyncSocketReader(asio::ip::tcp::socket&& 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);
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_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();
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);
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));
}
if (delimiter_pos == string::npos) {
throw runtime_error("line exceeds max length");
if (delimiter_pos == std::string::npos) {
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.
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);
co_return ret;
}
asio::awaitable<string> AsyncSocketReader::read_data(size_t size) {
string ret;
asio::awaitable<std::string> AsyncSocketReader::read_data(size_t size) {
std::string ret;
if (this->pending_data.size() == size) {
this->pending_data.swap(ret);
} 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));
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) {
deque<string> local_owned_data;
std::deque<std::string> local_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);
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);
co_await timer.async_wait(asio::use_awaitable);
}
+1 -1
View File
@@ -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...>;
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)
auto promise = std::make_shared<AsyncPromise<ReturnT>>();
asio::post(pool, [bound = std::move(bound), promise]() mutable {
+18 -21
View File
@@ -7,8 +7,6 @@
#include "Text.hh"
#include "Types.hh"
using namespace std;
template <bool BE>
struct BMLHeaderT {
parray<uint8_t, 0x04> unknown_a1;
@@ -42,13 +40,13 @@ void BMLArchive::load_t() {
const auto& entry = r.get<BMLHeaderEntryT<BE>>();
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;
offset = (offset + entry.compressed_size + 0x1F) & (~0x1F);
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;
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)
: data(data) {
BMLArchive::BMLArchive(std::shared_ptr<const std::string> data, bool big_endian) : data(data) {
if (big_endian) {
this->load_t<true>();
} 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;
}
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 {
const auto& entry = this->entries.at(name);
return make_pair(this->data->data() + entry.offset, entry.size);
} catch (const out_of_range&) {
throw out_of_range("BML does not contain file: " + name);
return std::make_pair(this->data->data() + entry.offset, entry.size);
} catch (const std::out_of_range&) {
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 {
const auto& entry = this->entries.at(name);
return make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
} catch (const out_of_range&) {
throw out_of_range("BML does not contain file: " + name);
return std::make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
} catch (const std::out_of_range&) {
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 {
const auto& entry = this->entries.at(name);
return this->data->substr(entry.offset, entry.size);
} catch (const out_of_range&) {
throw out_of_range("BML does not contain file: " + name);
} catch (const std::out_of_range&) {
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 {
const auto& entry = this->entries.at(name);
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
} catch (const out_of_range&) {
throw out_of_range("BML does not contain file: " + name);
} catch (const std::out_of_range&) {
throw std::out_of_range("BML does not contain file: " + name);
}
}
+10 -12
View File
@@ -7,8 +7,6 @@
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
using namespace std;
BattleParamsIndex::AttackData BattleParamsIndex::AttackData::from_json(const phosg::JSON& json) {
return AttackData{
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++) {
const auto& e = this->stats[static_cast<size_t>(difficulty)][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)) {
if (!names_str.empty()) {
names_str += ", ";
@@ -290,17 +288,17 @@ const BattleParamsIndex::Table& JSONBattleParamsIndex::get_table(bool solo, Epis
case Episode::EP4:
return this->tables[!!solo][2];
default:
throw invalid_argument("invalid episode");
throw std::invalid_argument("invalid episode");
}
}
BinaryBattleParamsIndex::BinaryBattleParamsIndex(
shared_ptr<const string> data_on_ep1,
shared_ptr<const string> data_on_ep2,
shared_ptr<const string> data_on_ep4,
shared_ptr<const string> data_off_ep1,
shared_ptr<const string> data_off_ep2,
shared_ptr<const string> data_off_ep4) {
std::shared_ptr<const std::string> data_on_ep1,
std::shared_ptr<const std::string> data_on_ep2,
std::shared_ptr<const std::string> data_on_ep4,
std::shared_ptr<const std::string> data_off_ep1,
std::shared_ptr<const std::string> data_off_ep2,
std::shared_ptr<const std::string> data_off_ep4) {
this->files[0][0].data = data_on_ep1;
this->files[0][1].data = data_on_ep2;
this->files[0][2].data = data_on_ep4;
@@ -312,7 +310,7 @@ BinaryBattleParamsIndex::BinaryBattleParamsIndex(
for (uint8_t episode = 0; episode < 3; episode++) {
auto& file = this->files[is_solo][episode];
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={})",
sizeof(Table), file.data->size(), is_solo, episode));
}
@@ -330,6 +328,6 @@ const BattleParamsIndex::Table& BinaryBattleParamsIndex::get_table(bool solo, Ep
case Episode::EP4:
return *this->files[!!solo][2].table;
default:
throw invalid_argument("invalid episode");
throw std::invalid_argument("invalid episode");
}
}
+20 -22
View File
@@ -12,14 +12,12 @@
#include "StaticGameData.hh"
#include "Version.hh"
using namespace std;
extern bool use_terminal_colors;
Channel::Channel(
Version version,
Language language,
const string& name,
const std::string& name,
phosg::TerminalFormat terminal_send_color,
phosg::TerminalFormat terminal_recv_color,
bool censor_received_credentials,
@@ -49,7 +47,7 @@ void Channel::send(
size += b.second;
}
string send_data;
std::string send_data;
size_t logical_size;
size_t send_data_size = 0;
switch (this->version) {
@@ -113,12 +111,12 @@ void Channel::send(
}
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
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);
@@ -164,10 +162,10 @@ void Channel::send(
}
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);
}
@@ -182,7 +180,7 @@ void Channel::send(const void* data, size_t size, bool 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);
}
@@ -196,7 +194,7 @@ asio::awaitable<Channel::Message> Channel::recv() {
size_t command_logical_size = header.size(version);
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
@@ -205,7 +203,7 @@ asio::awaitable<Channel::Message> Channel::recv() {
? ((command_logical_size + 7) & ~7)
: 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());
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::unique_ptr<asio::ip::tcp::socket>&& sock,
Version version,
Language language,
const string& name,
const std::string& name,
phosg::TerminalFormat terminal_send_color,
phosg::TerminalFormat terminal_recv_color,
bool censor_received_credentials,
bool censor_sent_credentials) {
shared_ptr<SocketChannel> ret(new SocketChannel(
std::shared_ptr<SocketChannel> ret(new SocketChannel(
io_context,
std::move(sock),
version,
@@ -296,7 +294,7 @@ SocketChannel::SocketChannel(
std::unique_ptr<asio::ip::tcp::socket>&& sock,
Version version,
Language language,
const string& name,
const std::string& name,
phosg::TerminalFormat terminal_send_color,
phosg::TerminalFormat terminal_recv_color,
bool censor_received_credentials,
@@ -320,7 +318,7 @@ void SocketChannel::disconnect() {
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) {
this->outbound_data.emplace_back(std::move(data));
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) {
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);
}
@@ -339,11 +337,11 @@ asio::awaitable<void> SocketChannel::send_task() {
auto this_sh = this->shared_from_this();
while (this->sock->is_open()) {
deque<string> to_send;
std::deque<std::string> to_send;
to_send.swap(this->outbound_data);
if (!to_send.empty()) {
vector<asio::const_buffer> bufs;
std::vector<asio::const_buffer> bufs;
bufs.reserve(to_send.size());
for (const auto& it : to_send) {
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) {
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;
peer2->peer = peer1;
@@ -400,7 +398,7 @@ void PeerChannel::disconnect() {
this->send_buffer_nonempty_signal.set();
}
void PeerChannel::send_raw(string&& data) {
void PeerChannel::send_raw(std::string&& data) {
auto peer = this->peer.lock();
if (peer) {
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();
}
} else if (!this->peer.lock()) {
throw runtime_error("Channel peer has disconnected");
throw std::runtime_error("Channel peer has disconnected");
}
}
}
+164 -174
View File
File diff suppressed because it is too large Load Diff
+9 -11
View File
@@ -5,9 +5,7 @@
#include "Client.hh"
using namespace std;
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
ChoiceSearchCategory{
.id = 0x0001,
.name = "Level",
@@ -24,7 +22,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
{0x0009, "Level 121-160"},
{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) {
return true;
}
@@ -75,18 +73,18 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
{0x0008, "FOnewm"},
{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) {
case 0x0000:
return true;
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:
return target_c->character_file()->disp.visual.class_flags & 0x40;
return target_c->character_file()->disp.visual.sh.class_flags & 0x40;
case 0x0012:
return target_c->character_file()->disp.visual.class_flags & 0x80;
return target_c->character_file()->disp.visual.sh.class_flags & 0x80;
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"},
{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) {
return true;
}
@@ -142,7 +140,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
{0x0005, "Battle"},
{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);
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
},
+129 -134
View File
@@ -16,11 +16,9 @@
#include "Server.hh"
#include "Version.hh"
using namespace std;
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) {
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);
break;
default:
throw logic_error("invalid game version");
throw std::logic_error("invalid game version");
}
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);
break;
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(
shared_ptr<GameServer> server,
shared_ptr<Channel> channel,
std::shared_ptr<GameServer> server,
std::shared_ptr<Channel> channel,
ServerBehavior server_behavior)
: server(server),
id(next_id++),
id(next_client_id++),
log(std::format("[C-{:X}] ", this->id), client_log.min_level),
channel(channel),
server_behavior(server_behavior),
@@ -239,13 +237,12 @@ Client::~Client() {
}
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);
if (player) {
string name_str = player->disp.name.decode(this->language());
size_t level = player->disp.stats.level + 1;
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", this->id, name_str, level, default_name);
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}",
this->id, player->disp.visual.name.decode(this->language()), player->disp.stats.level + 1, default_name);
} else {
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
be_uint64_t timestamp = phosg::now();
this->channel->send(0x1D, 0x00, &timestamp, sizeof(be_uint64_t));
} catch (const exception& e) {
} catch (const std::exception& e) {
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();
if (!server) {
throw logic_error("server is deleted");
throw std::logic_error("server is deleted");
}
return server->get_state();
}
shared_ptr<Lobby> Client::require_lobby() const {
std::shared_ptr<Lobby> Client::require_lobby() const {
auto l = this->lobby.lock();
if (!l) {
throw runtime_error("client not in any lobby");
throw std::runtime_error("client not in any lobby");
}
return l;
}
shared_ptr<const TeamIndex::Team> Client::team() const {
std::shared_ptr<const TeamIndex::Team> Client::team() const {
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) {
@@ -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
if (p) {
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) {
this->log.info_f("Updating player name in team config");
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(
shared_ptr<const IntegralExpression> expr,
shared_ptr<const Lobby> game,
std::shared_ptr<const IntegralExpression> expr,
std::shared_ptr<const Lobby> game,
uint8_t event,
Difficulty difficulty,
size_t num_players,
@@ -376,7 +373,7 @@ bool Client::evaluate_quest_availability_expression(
return true;
}
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();
IntegralExpression::Env env = {
@@ -389,15 +386,14 @@ bool Client::evaluate_quest_availability_expression(
};
int64_t ret = expr->evaluate(env);
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;
}
bool Client::can_see_quest(
shared_ptr<const Quest> q,
shared_ptr<const Lobby> game,
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
Difficulty difficulty,
size_t num_players,
@@ -417,8 +413,8 @@ bool Client::can_see_quest(
}
bool Client::can_play_quest(
shared_ptr<const Quest> q,
shared_ptr<const Lobby> game,
std::shared_ptr<const Quest> q,
std::shared_ptr<const Lobby> game,
uint8_t event,
Difficulty difficulty,
size_t num_players,
@@ -450,7 +446,7 @@ bool Client::can_use_chat_commands() const {
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;
auto s = this->require_server_state();
@@ -472,116 +468,116 @@ void Client::set_login(shared_ptr<Login> login) {
// 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);
}
string Client::system_filename() const {
std::string Client::system_filename() const {
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) {
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);
}
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) {
this->load_all_files();
}
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) {
throw runtime_error("system file is not loaded");
throw std::runtime_error("system file is not loaded");
}
return this->system_data;
}
void Client::save_system_file() const {
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);
this->log.info_f("Saved system file {}", filename);
}
// 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);
}
string Client::guild_card_filename() const {
std::string Client::guild_card_filename() const {
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) {
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);
}
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) {
this->load_all_files();
}
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) {
throw runtime_error("account data is not loaded");
throw std::runtime_error("account data is not loaded");
}
return this->guild_card_data;
}
void Client::save_guild_card_file() const {
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);
this->log.info_f("Saved Guild Card file {}", filename);
}
// 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()) {
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) {
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);
}
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_{}_{}.{}",
account_id, index, is_ep3 ? "pso3char" : "psochar");
}
string Client::character_filename() const {
std::string Client::character_filename() const {
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) {
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);
}
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) {
return this->overlay_character_data;
}
if (!this->character_data && allow_load) {
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();
if (!this->character_data) {
@@ -591,35 +587,35 @@ shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allo
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) {
return this->overlay_character_data;
}
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;
}
void Client::save_character_file(
const string& filename,
shared_ptr<const PSOBBBaseSystemFile> system,
shared_ptr<const PSOBBCharacterFile> character) {
const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> system,
std::shared_ptr<const PSOBBCharacterFile> character) {
PSOCHARFile::save(filename, system, character);
}
void Client::save_ep3_character_file(
const string& filename,
const std::string& filename,
const PSOGCEp3CharacterFile::Character& character) {
phosg::save_file(filename, &character, sizeof(character));
}
void Client::save_character_file() {
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()) {
throw logic_error("no character file loaded");
throw std::logic_error("no character file loaded");
}
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
@@ -643,18 +639,18 @@ void Client::save_character_file() {
void Client::create_character_file(
uint32_t guild_card_number,
Language language,
const PlayerDispDataBBPreview& preview,
shared_ptr<const LevelTable> level_table) {
const PlayerVisualConfigV4& visual,
std::shared_ptr<const LevelTable> level_table) {
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->log.info_f("Deleting bank file");
this->bank_data.reset();
std::filesystem::remove(this->bank_filename());
}
void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_ptr<const LevelTable> level_table) {
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
void Client::create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table) {
this->overlay_character_data = std::make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
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.tp_from_materials = 0;
uint32_t target_level = clamp<uint32_t>(rules->char_level, 0, 199);
uint8_t char_class = this->overlay_character_data->disp.visual.char_class;
uint32_t target_level = std::clamp<uint32_t>(rules->char_level, 0, 199);
uint8_t char_class = this->overlay_character_data->disp.visual.sh.char_class;
auto& stats = this->overlay_character_data->disp.stats;
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(
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);
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;
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;
level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.char_class);
level_table->advance_to_level(overlay->disp.stats, tpl.level, 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.sh.char_class);
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.attack_range = 10.0;
overlay->disp.stats.exp = stats_delta.exp;
@@ -752,9 +748,9 @@ void Client::create_challenge_overlay(
// 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()) {
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) {
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) {
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) {
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);
}
std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
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()) {
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
auto filename = this->bank_filename();
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->log.info_f("Loaded bank data from {}", filename);
} 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");
} else if (this->bb_bank_character_index >= 0) {
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;
this->bank_data = std::make_shared<PlayerBank>(character->bank);
this->log.info_f("Using bank data from {}", filename);
} else {
// 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");
}
}
@@ -825,14 +822,14 @@ std::shared_ptr<const PlayerBank> Client::bank_file(bool throw_if_missing) const
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");
bank.save(f.get());
}
void Client::save_bank_file() const {
if (!this->bank_data) {
throw logic_error("no bank file loaded");
throw std::logic_error("no bank file loaded");
}
auto filename = this->bank_filename();
this->save_bank_file(filename, *this->bank_data);
@@ -854,30 +851,28 @@ void Client::change_bank(ssize_t index) {
// Legacy files
string Client::legacy_account_filename() const {
std::string Client::legacy_account_filename() const {
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) {
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);
}
string Client::legacy_player_filename() const {
std::string Client::legacy_player_filename() const {
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) {
throw logic_error("client is not logged in");
throw std::logic_error("client is not logged in");
}
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(
"system/players/player_{}_{}.nsc",
this->login->bb_license->username,
static_cast<ssize_t>(this->bb_character_index + 1));
return std::format("system/players/player_{}_{}.nsc",
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) {
@@ -891,20 +886,20 @@ void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_sende
void Client::load_all_files() {
if (this->version() != Version::BB_V4) {
this->system_data = make_shared<PSOBBBaseSystemFile>();
this->character_data = make_shared<PSOBBCharacterFile>();
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
this->bank_data = make_shared<PlayerBank>();
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
this->character_data = std::make_shared<PSOBBCharacterFile>();
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>();
this->bank_data = std::make_shared<PlayerBank>();
return;
}
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) {
string sys_filename = this->system_filename();
std::string sys_filename = this->system_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);
} else {
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)) {
string char_filename = this->character_filename();
std::string char_filename = this->character_filename();
if (std::filesystem::is_regular_file(char_filename)) {
auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data);
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 (!this->system_data) {
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->log.info_f("Loaded system data from {}", char_filename);
@@ -936,9 +931,9 @@ void Client::load_all_files() {
}
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)) {
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->log.info_f("Loaded Guild Card data from {}", card_filename);
} 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 (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
string nsa_filename = this->legacy_account_filename();
shared_ptr<LegacySavedAccountDataBB> nsa_data;
std::string nsa_filename = this->legacy_account_filename();
std::shared_ptr<LegacySavedAccountDataBB> nsa_data;
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)) {
throw runtime_error("account data header is incorrect");
throw std::runtime_error("account data header is incorrect");
}
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);
}
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);
}
}
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)) {
auto nsc_data = phosg::load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
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.unknown_a1.clear(0);
} 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->disp = nsc_data.disp;
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->bank = nsc_data.bank;
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.present = 1;
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.char_class = nsc_data.disp.visual.char_class;
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.sh.char_class;
this->character_data->auto_reply = nsc_data.auto_reply;
this->character_data->info_board = nsc_data.info_board;
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
// guild_card_data are always non-null, but character_data may still be null
if (!this->system_data) {
this->system_data = make_shared<PSOBBBaseSystemFile>();
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
auto s = this->require_server_state();
if (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");
}
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");
}
@@ -1042,7 +1037,7 @@ void Client::load_all_files() {
if (this->character_data) {
// 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->login->account->auto_reply_message = this->character_data->auto_reply.decode();
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()));
Language lang = this->language();
@@ -1064,7 +1059,7 @@ void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> cha
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 limits = s->item_stack_limits(this->version());
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) {
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->update_character_data_after_load(this->character_data);
this->v1_v2_last_reported_disp.reset();
}
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);
auto ch = make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
std::shared_ptr<PSOGCEp3CharacterFile::Character> Client::load_ep3_backup_character(uint32_t account_id, size_t index) {
std::string filename = this->backup_character_filename(account_id, index, true);
auto ch = std::make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
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->v1_v2_last_reported_disp.reset();
return ch;
+4 -4
View File
@@ -10,7 +10,6 @@
#include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh"
#include "Episode3/Tournament.hh"
#include "FileContentsCache.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "PatchFileIndex.hh"
@@ -28,7 +27,7 @@ struct Lobby;
class Parsed6x70Data;
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<PSOGCEp3CharacterFile::Character> ep3_character;
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;
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::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)
std::unique_ptr<PendingItemTrade> pending_item_trade;
std::unique_ptr<PendingCardTrade> pending_card_trade;
@@ -316,7 +316,7 @@ public:
void create_character_file(
uint32_t guild_card_number,
Language language,
const PlayerDispDataBBPreview& preview,
const PlayerVisualConfigV4& visual,
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);
+40 -42
View File
@@ -18,8 +18,6 @@
#include "Compression.hh"
#include "Loggers.hh"
using namespace std;
using Arch = ClientFunctionIndex::Function::Architecture;
const char* name_for_architecture(Arch arch) {
@@ -31,7 +29,7 @@ const char* name_for_architecture(Arch arch) {
case Arch::X86:
return "x86";
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:
return SPECIFIC_VERSION_X86_INDETERMINATE;
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>
string ClientFunctionIndex::Function::generate_client_command_t(
const unordered_map<string, uint32_t>& label_writes,
std::string ClientFunctionIndex::Function::generate_client_command_t(
const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data,
size_t suffix_size,
uint32_t override_relocations_offset) const {
@@ -102,11 +100,11 @@ string ClientFunctionIndex::Function::generate_client_command_t(
phosg::StringWriter w;
if (!label_writes.empty()) {
string modified_code = this->code;
std::string modified_code = this->code;
for (const auto& it : label_writes) {
size_t offset = this->label_offsets.at(it.first);
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;
}
@@ -143,8 +141,8 @@ string ClientFunctionIndex::Function::generate_client_command_t(
return std::move(w.str());
}
string ClientFunctionIndex::Function::generate_client_command(
const unordered_map<string, uint32_t>& label_writes,
std::string ClientFunctionIndex::Function::generate_client_command(
const std::unordered_map<std::string, uint32_t>& label_writes,
const void* suffix_data,
size_t suffix_size,
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)) {
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
} 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;
struct Line {
std::string text;
@@ -170,7 +168,7 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
auto& line = lines.emplace_back();
line.text = std::move(line_text);
string stripped_line = line.text;
std::string stripped_line = line.text;
phosg::strip_whitespace(stripped_line);
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 = "";
unordered_map<uint32_t, std::string> ret;
std::unordered_map<uint32_t, std::string> ret;
for (uint32_t specific_version : all_specific_versions) {
std::deque<std::string> version_lines;
bool include_current_line = true;
@@ -220,15 +218,15 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
} else {
std::string line_text = line.text;
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);
if (end_offset == string::npos) {
throw runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
if (end_offset == std::string::npos) {
throw std::runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
str_for_specific_version(specific_version), line_znum + 1));
}
auto tokens = phosg::split(line_text.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
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));
}
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;
}
ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_any_failure) {
map<string, string> source_files;
ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise_on_any_failure) {
std::map<std::string, std::string> source_files;
std::function<void(const std::string&)> add_directory = [&](const std::string& dir) -> void {
for (const auto& item : std::filesystem::directory_iterator(dir)) {
string item_name = item.path().filename().string();
string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
std::string item_name = item.path().filename().string();
std::string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
if (std::filesystem::is_directory(item_path)) {
add_directory(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);
unordered_map<string, string> include_cache;
std::unordered_map<std::string, std::string> include_cache;
uint32_t last_menu_item_id = 0;
for (const auto& [source_filename, source] : source_files) {
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) {
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->specific_version = specific_version;
fn->menu_item_id = ++last_menu_item_id;
fn->arch = architecture_for_specific_version(fn->specific_version);
try {
unordered_set<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::unordered_set<std::string> get_include_stack;
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 {
return get_with_sv_fallback(include_cache, name, specific_version);
} 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");
if (it != source_files.end()) {
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)) {
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);
break;
default:
throw runtime_error("unknown architecture");
throw std::runtime_error("unknown architecture");
}
if (client_functions_log.should_log(phosg::LogLevel::L_DEBUG)) {
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);
} 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)));
};
@@ -365,7 +363,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
assembled = ResourceDASM::SH4Emulator::assemble(source, get_include);
break;
default:
throw runtime_error("invalid architecture");
throw std::runtime_error("invalid architecture");
}
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") {
fn->show_return_value = true;
} else {
throw runtime_error("unknown metadata key: " + key);
throw std::runtime_error("unknown metadata key: " + key);
}
}
try {
fn->entrypoint_offset_offset = fn->label_offsets.at("entry_ptr");
} catch (const out_of_range&) {
throw runtime_error("code does not contain entry_ptr label");
} catch (const std::out_of_range&) {
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) {
if (it.first.starts_with("reloc")) {
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) {
uint32_t delta = it - prev_index;
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);
prev_index = it;
}
} catch (const exception& e) {
} catch (const std::exception& e) {
if (raise_on_any_failure) {
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,
const std::unordered_set<std::string>& server_auto_patches_enabled,
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);
auto map_it = this->functions_by_specific_version.find(specific_version);
if (map_it != this->functions_by_specific_version.end()) {
for (auto [name, fn] : map_it->second) {
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 += fn->long_name.empty() ? fn->short_name : fn->long_name;
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) {
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()) {
struct {
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 specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
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 specific_version = 0x33004A54 | (*game_code2 << 16);
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';
}
+65 -43
View File
@@ -412,18 +412,18 @@ struct S_UpdateClientConfig_DC_PC_04 {
le_uint32_t guild_card_number = 0;
} __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 guild_card_number = 0;
// 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;
} __packed_ws__(S_UpdateClientConfig_V3_04, 0x28);
parray<uint8_t, ClientConfigBytes> client_config;
} __attribute__((packed));
struct S_UpdateClientConfig_BB_04 {
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
parray<uint8_t, 0x28> client_config;
} __packed_ws__(S_UpdateClientConfig_BB_04, 0x30);
using S_UpdateClientConfig_V3_04 = S_UpdateClientConfigT_V3_BB_04<0x20>;
using S_UpdateClientConfig_BB_04 = S_UpdateClientConfigT_V3_BB_04<0x28>;
check_struct_size(S_UpdateClientConfig_V3_04, 0x28);
check_struct_size(S_UpdateClientConfig_BB_04, 0x30);
// 05: Disconnect
// Internal name: SndLogout
@@ -1053,13 +1053,13 @@ struct PlayerRecordsEntry_BB {
struct C_CharacterData_DCv1_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */
} __packed_ws__(C_CharacterData_DCv1_61_98, 0x041C);
struct C_CharacterData_DCv2_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */ PlayerRecordsEntry_DC records;
/* 04D8 */ ChoiceSearchConfig choice_search_config;
/* 04F0 */
@@ -1067,7 +1067,7 @@ struct C_CharacterData_DCv2_61_98 {
struct C_CharacterData_PC_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */ PlayerRecordsEntry_PC records;
/* 0510 */ ChoiceSearchConfig choice_search_config;
/* 0528 */ parray<le_uint32_t, 0x1E> blocked_senders;
@@ -1079,7 +1079,7 @@ struct C_CharacterData_PC_61_98 {
struct C_CharacterData_GCNTE_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */ PlayerRecordsEntry_DC records;
/* 04D8 */ ChoiceSearchConfig choice_search_config;
/* 04F0 */ parray<le_uint32_t, 0x1E> blocked_senders;
@@ -1091,7 +1091,7 @@ struct C_CharacterData_GCNTE_61_98 {
struct C_CharacterData_V3_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */ PlayerRecordsEntry_V3 records;
/* 0538 */ ChoiceSearchConfig choice_search_config;
/* 0550 */ pstring<TextEncoding::MARKED, 0xAC> info_board;
@@ -1104,7 +1104,7 @@ struct C_CharacterData_V3_61_98 {
struct C_CharacterData_Ep3_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */ PlayerRecordsEntry_V3 records;
/* 0538 */ ChoiceSearchConfig choice_search_config;
/* 0550 */ pstring<TextEncoding::MARKED, 0xAC> info_board;
@@ -1117,7 +1117,7 @@ struct C_CharacterData_Ep3_61_98 {
struct C_CharacterData_BB_61_98 {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataBB disp;
/* 034C */ PlayerDispDataV4 disp;
/* 04DC */ PlayerRecordsEntry_BB records;
/* 0638 */ ChoiceSearchConfig choice_search_config;
/* 0650 */ pstring<TextEncoding::UTF16, 0xAC> info_board;
@@ -1139,17 +1139,37 @@ struct C_CharacterData_BB_61_98 {
// 64 (S->C): Join game
// 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
// 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
template <typename LobbyDataT>
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.
struct Ep3PlayerEntry {
PlayerInventory inventory;
PlayerDispDataDCPCV3 disp;
PlayerDispDataV123 disp;
} __packed_ws__(Ep3PlayerEntry, 0x41C);
parray<Ep3PlayerEntry, 4> players_ep3;
} __packed_ws__(S_JoinGame_Ep3_64, 0x1180);
@@ -1272,10 +1292,10 @@ struct S_JoinLobbyT {
return offsetof(S_JoinLobbyT, entries) + used_entries * sizeof(Entry);
}
} __attribute__((packed));
using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT<LobbyFlagsDCNTE, PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>;
using S_JoinLobby_PC_65_67_68 = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataPC, PlayerDispDataDCPCV3>;
using S_JoinLobby_DC_GC_65_67_68_Ep3_EB = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>;
using S_JoinLobby_BB_65_67_68 = S_JoinLobbyT<LobbyFlags, PlayerLobbyDataBB, PlayerDispDataBB>;
using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT<LobbyFlagsDCNTE, PlayerLobbyDataDCGC, PlayerDispDataV123>;
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, PlayerDispDataV123>;
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_PC_65_67_68, 0x339C);
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 {
PlayerLobbyDataXB lobby_data;
PlayerInventory inventory;
PlayerDispDataDCPCV3 disp;
PlayerDispDataV123 disp;
} __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
// 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
// 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.
// 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
// 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.
@@ -2623,7 +2643,7 @@ struct C_GuildCardDataRequest_BB_03DC {
// 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.
// 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)
@@ -2928,7 +2948,7 @@ struct S_CardBattleTableConfirmation_Ep3_E5 {
struct SC_PlayerPreview_CreateCharacter_BB_00E5 {
le_int32_t character_index = 0;
PlayerDispDataBBPreview preview;
PlayerDispDataV4Preview preview;
} __packed_ws__(SC_PlayerPreview_CreateCharacter_BB_00E5, 0x80);
// E6 (C->S): Spectator team control (Episode 3)
@@ -3014,7 +3034,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 {
struct PlayerEntry {
/* 0000 */ PlayerLobbyDataDCGC lobby_data;
/* 0020 */ PlayerInventory inventory;
/* 036C */ PlayerDispDataDCPCV3 disp;
/* 036C */ PlayerDispDataV123 disp;
/* 043C */
} __packed_ws__(PlayerEntry, 0x43C);
/* 0080 */ parray<PlayerEntry, 4> players;
@@ -4741,7 +4761,7 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 {
/* 0054 */ PlayerHoldState_DCProtos hold_state;
/* 0064 */ le_uint32_t area = 0;
/* 0068 */ le_uint32_t player_flags = 0;
/* 006C */ PlayerVisualConfig visual;
/* 006C */ PlayerVisualConfigV123 visual;
/* 00BC */ PlayerStats stats;
/* 00E0 */ le_uint32_t num_items = 0;
/* 00E4 */ parray<PlayerInventoryItem, 0x1E> items;
@@ -4759,7 +4779,7 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 {
/* 0060 */ PlayerHoldState_DCProtos hold_state;
/* 0070 */ le_uint32_t area = 0;
/* 0074 */ le_uint32_t player_flags = 0;
/* 0078 */ PlayerVisualConfig visual;
/* 0078 */ PlayerVisualConfigV123 visual;
/* 00C8 */ PlayerStats stats;
/* 00EC */ le_uint32_t num_items = 0;
/* 00F0 */ parray<PlayerInventoryItem, 0x1E> items;
@@ -4790,13 +4810,13 @@ struct G_6x70_Base_V1 {
/* 00AC */ le_uint32_t area = 0;
/* 00B0 */ le_uint32_t player_flags = 0;
/* 00B4 */ parray<uint8_t, 0x14> technique_levels_v1 = 0xFF; // Last byte is uninitialized
/* 00C8 */ PlayerVisualConfig visual;
/* 0118 */
} __packed_ws__(G_6x70_Base_V1, 0x118);
/* 00C8 */
} __packed_ws__(G_6x70_Base_V1, 0xC8);
struct 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;
/* 00D0 */ PlayerVisualConfigV123 visual;
/* 0120 */ PlayerStats stats;
/* 0144 */ le_uint32_t num_items = 0;
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
@@ -4807,6 +4827,7 @@ struct G_SyncPlayerDispAndInventory_DC_PC_6x70 {
struct G_SyncPlayerDispAndInventory_GC_6x70 {
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)};
/* 0008 */ G_6x70_Base_V1 base;
/* 00D0 */ PlayerVisualConfigV123 visual;
/* 0120 */ PlayerStats stats;
/* 0144 */ le_uint32_t num_items = 0;
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
@@ -4817,6 +4838,7 @@ struct G_SyncPlayerDispAndInventory_GC_6x70 {
struct G_SyncPlayerDispAndInventory_XB_6x70 {
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)};
/* 0008 */ G_6x70_Base_V1 base;
/* 00D0 */ PlayerVisualConfigV123 visual;
/* 0120 */ PlayerStats stats;
/* 0144 */ le_uint32_t num_items = 0;
/* 0148 */ parray<PlayerInventoryItem, 0x1E> items;
@@ -4830,7 +4852,7 @@ struct G_SyncPlayerDispAndInventory_XB_6x70 {
struct G_SyncPlayerDispAndInventory_BB_6x70 {
/* 0000 */ G_ExtendedHeaderT<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)};
/* 0008 */ G_6x70_Base_V1 base;
/* 0120 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
/* 00D0 */ PlayerVisualConfigV4 visual;
/* 0140 */ PlayerStats stats;
/* 0164 */ le_uint32_t num_items = 0;
/* 0168 */ parray<PlayerInventoryItem, 0x1E> items;
@@ -5200,10 +5222,10 @@ struct G_SetChallengeTime_6x95 {
struct G_SelectChallengeModeFailureOption_6x97 {
G_UnusedHeader header;
le_uint32_t unused1 = 0;
le_uint32_t client_id = 0;
le_uint32_t is_retry = 0;
le_uint32_t unused1 = 0;
le_uint32_t unused2 = 0;
le_uint32_t unused3 = 0;
} __packed_ws__(G_SelectChallengeModeFailureOption_6x97, 0x14);
// 6x98: Unknown
@@ -5747,7 +5769,7 @@ struct G_ChangeLobbyMusic_Ep3_6xBF {
// 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
// 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 {
G_ClientIDHeader header;
+42 -44
View File
@@ -6,8 +6,6 @@
#include "StaticGameData.hh"
#include "Types.hh"
using namespace std;
template <typename IntT, size_t Count>
phosg::JSON to_json(const parray<IntT, Count>& v) {
auto ret = phosg::JSON::list();
@@ -20,7 +18,7 @@ phosg::JSON to_json(const parray<IntT, Count>& v) {
template <typename IntT, size_t Count>
void from_json_into(const phosg::JSON& json, parray<IntT, Count>& ret) {
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++) {
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>
void from_json_into(const phosg::JSON& json, parray<CommonItemSet::Table::Range<IntT>, Count>& ret) {
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++) {
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 {
const auto& l = json.as_list();
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.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>
void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1>& ret) {
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++) {
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>
void from_json_into(const phosg::JSON& json, parray<parray<CommonItemSet::Table::Range<IntT>, Count2>, Count1>& ret) {
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++) {
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>()) {
try {
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 {
@@ -151,7 +149,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
try {
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 {
@@ -163,7 +161,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
try {
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 {
@@ -208,13 +206,13 @@ void CommonItemSet::Table::print(FILE* stream) const {
def.rt_index, phosg::name_for_enum(enemy_type),
meseta_range.min, meseta_range.max, drop_prob, 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",
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"};
phosg::fwrite_fmt(stream, "Base weapon config:\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);
}
static const array<const char*, 19> technique_names = {
static const std::array<const char*, 19> technique_names = {
"FOIE ",
"GIFOIE ",
"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 {
string ret = "";
std::string ret = "";
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
try {
const auto& range = table.at(enemy_type);
@@ -400,13 +398,13 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
ret += ",";
}
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;
};
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>()) {
try {
uint8_t value = table.at(enemy_type);
@@ -414,7 +412,7 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
ret += ",";
}
ret += std::format("{}={}", phosg::name_for_enum(enemy_type), value);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
}
return ret;
@@ -620,7 +618,7 @@ phosg::JSON CommonItemSet::json() const {
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
auto table = this->get_table(episode, mode, difficulty, section_id);
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_section_id(section_id));
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& difficulty : ALL_DIFFICULTIES_V234) {
for (uint8_t section_id = 0; section_id < 10; section_id++) {
shared_ptr<const Table> this_table;
shared_ptr<const Table> other_table;
std::shared_ptr<const Table> this_table;
std::shared_ptr<const Table> other_table;
try {
this_table = this->get_table(episode, mode, difficulty, section_id);
} catch (const runtime_error&) {
} catch (const std::runtime_error&) {
}
try {
other_table = other.get_table(episode, mode, difficulty, section_id);
} catch (const runtime_error&) {
} catch (const std::runtime_error&) {
}
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));
}
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 {
try {
return this->tables.at(this->key_for_table(episode, mode, difficulty, section_id));
} catch (const out_of_range&) {
throw runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
} catch (const std::out_of_range&) {
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));
}
}
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 {
if (section_id != 0) {
// 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++) {
auto entry = pt_afs.get(static_cast<size_t>(difficulty) * 10 + section_id);
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::BATTLE, 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;
}
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++) {
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) {
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 = "";
switch (episode) {
case Episode::EP1:
@@ -885,7 +883,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
episode_token = "s";
break;
default:
throw runtime_error("invalid episode");
throw std::runtime_error("invalid episode");
}
return std::format(
"ItemPT{}{}{}{}.rel",
@@ -901,7 +899,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
phosg::StringReader r;
try {
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
if (episode == Episode::EP4) {
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;
}
}
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::BATTLE, 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) {
try {
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++) {
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
}
}
@@ -943,9 +941,9 @@ JSONCommonItemSet::JSONCommonItemSet(const phosg::JSON& json) {
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 key = this->key_for_table(episode, mode, difficulty, section_id);
this->tables.emplace(key, make_shared<Table>(prev_table, json.at(json_key), episode));
} catch (const runtime_error&) {
} catch (const out_of_range&) {
this->tables.emplace(key, std::make_shared<Table>(prev_table, json.at(json_key), episode));
} catch (const std::runtime_error&) {
} 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)));
}
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);
}
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
std::pair<const ToolRandomSet::WeightTableEntry8*, size_t>
ToolRandomSet::get_rare_recovery_table(size_t index) const {
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 {
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 {
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 {
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));
return make_pair(data, spec.entries_per_table);
return std::make_pair(data, spec.entries_per_table);
}
const parray<WeaponRandomSet::WeightTableEntry32, 6>*
@@ -1055,7 +1053,7 @@ const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_table(
bool favored,
uint8_t section_id) const {
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];
if (table.count == 0) {
+45 -48
View File
@@ -11,8 +11,6 @@
#include "Text.hh"
using namespace std;
template <>
const char* phosg::name_for_enum<CompressPhase>(CompressPhase v) {
switch (v) {
@@ -34,13 +32,13 @@ struct WindowIndex {
const uint8_t* data;
size_t size;
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)
: data(reinterpret_cast<const uint8_t*>(data)),
size(size),
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() {
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
// custom comparator to treat them as references to binary strings within the data.
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;
for (; a < end_a; a++, b++) {
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
};
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
// 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
@@ -108,7 +106,7 @@ struct WindowIndex {
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) {
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) {
this->buf[0] |= this->next_control_bit;
@@ -208,15 +206,15 @@ struct PRSPathNode {
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);
vector<PRSPathNode> nodes;
std::vector<PRSPathNode> nodes;
nodes.resize(in_size + 1);
nodes[0].bits_used = 18; // Stop command: 2 control bits and 2 data bytes
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
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) {
case PRSPathNode::CommandType::LITERAL:
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_data(in_data[offset]);
break;
case PRSPathNode::CommandType::SHORT_COPY: {
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;
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: {
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.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: {
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.flush_if_ready();
@@ -407,7 +405,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
break;
}
default:
throw logic_error("invalid copy type in shortest path");
throw std::logic_error("invalid copy type in shortest path");
}
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());
}
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);
}
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);
// 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) {
if (this->closed) {
throw logic_error("compressor is closed");
throw std::logic_error("compressor is closed");
}
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());
}
@@ -573,7 +571,7 @@ void PRSCompressor::advance() {
this->advance_extended_copy(backreference_offset, best_match_size);
} 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);
}
string& PRSCompressor::close() {
std::string& PRSCompressor::close() {
if (!this->closed) {
// Advance until all input is consumed
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);
} else {
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);
}
}
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);
prs.add(vdata, size);
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);
}
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);
LZSSInterleavedWriter w;
@@ -779,12 +777,12 @@ string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallb
}
case PRSPathNode::CommandType::NONE:
default:
throw logic_error("invalid command type");
throw std::logic_error("invalid command type");
}
w.flush_if_ready();
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++) {
@@ -804,7 +802,7 @@ string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallb
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);
}
@@ -846,7 +844,7 @@ PRSDecompressResult prs_decompress_with_meta(
if (allow_unterminated) {
return {std::move(w.str()), r.where()};
} else {
throw runtime_error("maximum output size exceeded");
throw std::runtime_error("maximum output size exceeded");
}
}
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.
size_t read_offset = w.size() + offset;
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++) {
if (max_output_size && w.size() == max_output_size) {
if (allow_unterminated) {
return {std::move(w.str()), r.where()};
} 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]);
@@ -900,16 +898,16 @@ PRSDecompressResult prs_decompress_with_meta(
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);
}
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);
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);
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;
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;
}
@@ -954,7 +952,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
if (allow_unterminated) {
return max_output_size;
} 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;
}
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);
}
@@ -1015,7 +1013,7 @@ void prs_disassemble(FILE* stream, const void* data, size_t size) {
}
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;
}
@@ -1043,11 +1041,10 @@ struct BC0PathNode {
size_t to_offset = 0;
};
string bc0_compress_optimal(
const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
std::string bc0_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);
vector<BC0PathNode> nodes;
std::vector<BC0PathNode> nodes;
nodes.resize(in_size + 1);
nodes[0].bits_used = 0;
@@ -1140,11 +1137,11 @@ string bc0_compress_optimal(
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);
}
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);
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());
}
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);
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
// 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());
}
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::StringWriter w;
@@ -1265,7 +1262,7 @@ string bc0_decompress(const void* data, size_t size) {
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());
}
+27 -28
View File
@@ -9,8 +9,6 @@
#include "PSOEncryption.hh"
using namespace std;
static const uint32_t primes1[] = {
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,
@@ -735,10 +733,10 @@ static const uint32_t primes3[] = {
static constexpr size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
static bool check_prime3(uint64_t prime3) {
static vector<bool> primes3_set;
static mutex primes3_init_mutex;
static std::vector<bool> primes3_set;
static std::mutex primes3_init_mutex;
if (primes3_set.empty()) {
lock_guard g(primes3_init_mutex);
std::lock_guard g(primes3_init_mutex);
if (primes3_set.empty()) {
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
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 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) {
return INVALID_PRODUCT;
}
@@ -828,20 +826,20 @@ static uint32_t encode_dc_serial_number_int(uint32_t 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) {
return make_pair(0, 0);
return std::make_pair(0, 0);
}
size_t domain_base = domain * 30;
if (subdomain != 0xFF) {
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 {
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);
if (serial_number == INVALID_PRODUCT) {
return false;
@@ -890,7 +888,7 @@ bool decoded_dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t doma
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);
if (serial_number == INVALID_PRODUCT) {
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);
}
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;
if (domain == 0) {
offset1 = 0x00;
@@ -914,7 +912,7 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
offset1 = 0x3C;
limit1 = 0x3F;
} else {
throw runtime_error("invalid domain");
throw std::runtime_error("invalid domain");
}
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 index3 = phosg::random_object<uint32_t>() % num_primes3;
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) {
ret.push_back(replace_char_reverse(ch));
}
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;
if (domain < 3) {
iter.domain = domain;
iter.end_domain = domain + 1;
} else if (domain != 0xFF) {
throw runtime_error("invalid domain");
throw std::runtime_error("invalid domain");
}
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.end_subdomain = subdomain + 1;
} else if (subdomain != 0xFF) {
throw runtime_error("invalid subdomain");
throw std::runtime_error("invalid subdomain");
}
uint32_t serial_number;
unordered_map<uint32_t, string> ret;
std::unordered_map<uint32_t, std::string> ret;
while ((serial_number = iter.next()) != 0) {
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
if (iter.index3 == 0) {
@@ -1015,7 +1013,7 @@ void dc_serial_number_speed_test(uint64_t seed) {
size_t num_disagreements = 0;
static constexpr size_t count = 0x1000;
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();
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);
}
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 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_steps_r.skip(1);
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();
}
@@ -1064,10 +1063,10 @@ string decrypt_dp_address_jpn(const string& executable, const string& values, co
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;
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);
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) {
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);
if (mask_key < 0) {
unordered_map<uint32_t, size_t> key_freq;
std::unordered_map<uint32_t, size_t> key_freq;
while (!r.eof()) {
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) {
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);
r.go(0);
+8 -10
View File
@@ -14,9 +14,7 @@
#include "NetworkAddresses.hh"
#include "ServerState.hh"
using namespace std;
DNSServer::DNSServer(shared_ptr<ServerState> state)
DNSServer::DNSServer(std::shared_ptr<ServerState> state)
: state(state) {}
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::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);
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) {
throw invalid_argument("query too small");
throw std::invalid_argument("query too small");
}
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;
string response;
std::string response;
response.append(data, 2);
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
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;
}
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);
}
asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::socket> sock) {
for (;;) {
string input(2048, 0);
std::string input(2048, 0);
asio::ip::udp::endpoint sender_ep;
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());
@@ -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)
? this->state->local_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);
}
}
+11 -13
View File
@@ -18,15 +18,13 @@
#include "Compression.hh"
#include "Loggers.hh"
using namespace std;
DOLFileIndex::DOLFileIndex(const string& directory) {
DOLFileIndex::DOLFileIndex(const std::string& directory) {
if (!std::filesystem::is_directory(directory)) {
client_functions_log.info_f("DOL file directory is missing");
return;
}
auto menu = make_shared<Menu>(MenuID::PROGRAMS, "Programs");
auto menu = std::make_shared<Menu>(MenuID::PROGRAMS, "Programs");
this->menu = menu;
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) {
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 {
auto dol = make_shared<File>();
auto dol = std::make_shared<File>();
dol->menu_item_id = next_menu_item_id++;
dol->name = name;
string path = directory + "/" + filename;
string file_data = phosg::load_file(path);
std::string path = directory + "/" + filename;
std::string file_data = phosg::load_file(path);
string description;
std::string description;
if (is_compressed_dol) {
size_t decompressed_size = prs_decompress_size(file_data);
@@ -66,8 +64,8 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
}
dol->data = std::move(w.str());
string compressed_size_str = phosg::format_size(file_data.size());
string decompressed_size_str = phosg::format_size(decompressed_size);
std::string compressed_size_str = phosg::format_size(file_data.size());
std::string decompressed_size_str = phosg::format_size(decompressed_size);
client_functions_log.debug_f("Loaded compressed DOL file {} ({} -> {})",
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());
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);
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);
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());
}
}
+50 -52
View File
@@ -22,12 +22,10 @@
#include "ReceiveSubcommands.hh"
#include "SendCommands.hh"
using namespace std;
static string random_name() {
string ret;
static std::string random_name() {
std::string ret;
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) {
ret.push_back(alphabet[phosg::random_object<size_t>() % alphabet.size()]);
}
@@ -87,40 +85,40 @@ DownloadSession::DownloadSession(
case Version::DC_V1:
case Version::DC_V2:
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;
case Version::PC_V2:
if (this->serial_number == 0 || this->access_key.empty()) {
throw runtime_error("missing credentials");
throw std::runtime_error("missing credentials");
}
break;
case Version::GC_V3:
if (this->serial_number == 0 || this->access_key.empty() || this->password.empty()) {
throw runtime_error("missing credentials");
throw std::runtime_error("missing credentials");
}
break;
case Version::XB_V3:
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;
case Version::BB_V4:
if (this->username.empty() || this->password.empty()) {
throw runtime_error("missing credentials");
throw std::runtime_error("missing credentials");
}
break;
default:
throw runtime_error("unsupported version");
throw std::runtime_error("unsupported version");
}
this->character->inventory.language = language;
}
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);
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->io_context,
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.access_key.encode(this->access_key);
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));
} 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.serial_number2 = ret.serial_number;
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
? ((this->version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_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.serial_number2 = ret.serial_number;
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;
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.serial_number2 = ret.serial_number;
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.external_ipv4_address = phosg::random_object<uint32_t>();
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));
} 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)) {
C_CharacterData_DCv1_61_98 ret;
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);
} else if (this->version == Version::DC_V2) {
C_CharacterData_DCv2_61_98 ret;
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.battle = this->character->battle_records;
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) {
C_CharacterData_PC_61_98 ret;
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.battle = this->character->battle_records;
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)) {
C_CharacterData_V3_61_98 ret;
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.battle = this->character->battle_records;
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);
} 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) {
case 0x03: {
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) {
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);
this->channel->crypt_in = 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_in = std::make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_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");
throw runtime_error("not yet implemented"); // Send 93
throw std::runtime_error("not yet implemented"); // Send 93
break;
}
@@ -297,17 +295,17 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
case 0x9B: {
const auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
if (uses_v3_encryption(this->version)) {
this->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
this->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
this->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_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})",
cmd.server_key, cmd.client_key);
} else if (!uses_v4_encryption(this->version)) {
this->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
this->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
this->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_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})",
cmd.server_key, cmd.client_key);
} 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) {
@@ -346,7 +344,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
this->send_93_9D_9E(true);
} 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);
} else {
throw runtime_error("unsupported version");
throw std::runtime_error("unsupported version");
}
} else if (msg.flag == 0 || msg.flag == 2) {
this->send_93_9D_9E(true);
} else {
throw runtime_error("login failed");
throw std::runtime_error("login failed");
}
break;
}
@@ -396,14 +394,14 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
case 0x92:
case 0x9C:
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);
break;
case 0x9F: {
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);
break;
@@ -477,11 +475,11 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
if (this->interactive) {
while (item_index == 0 || item_index > msg.flag) {
this->log.info_f("Choose response index:");
string input = phosg::fgets(stdin);
item_index = stoul(input, nullptr, 0);
std::string input = phosg::fgets(stdin);
item_index = std::stoul(input, nullptr, 0);
}
} else {
throw runtime_error("unhandled menu selection");
throw std::runtime_error("unhandled menu selection");
}
}
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);
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);
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->io_context,
std::move(sock),
@@ -649,12 +647,12 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
case 0xA6: {
auto handle_command = [&]<typename CmdT>() {
const auto& cmd = msg.check_size_t<CmdT>(0xFFFF);
string internal_name = cmd.filename.decode();
string filtered_name;
std::string internal_name = cmd.filename.decode();
std::string filtered_name;
for (char ch : internal_name) {
filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_');
}
string local_filename = std::format(
std::string local_filename = std::format(
"{}/{:016X}_{}_{}_{:c}_{}",
this->output_dir,
this->current_request,
@@ -677,7 +675,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
case 0x13:
case 0xA7: {
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)) {
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());
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 data_size = min<size_t>(cmd.data_size, allowed_block_size);
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 = std::min<size_t>(cmd.data_size, allowed_block_size);
size_t block_end_offset = block_offset + data_size;
if (block_end_offset > f.data.size()) {
f.data.resize(block_end_offset);
@@ -720,7 +718,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
}
case 0xAC: {
if (is_v1_or_v2(this->version)) {
throw runtime_error("unsupported version");
throw std::runtime_error("unsupported version");
}
this->on_request_complete();
break;
@@ -745,7 +743,7 @@ void DownloadSession::send_next_request() {
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):");
string input = phosg::fgets(stdin);
std::string input = phosg::fgets(stdin);
if (input.empty() || (input == "q\n")) {
this->channel->disconnect();
return;
@@ -753,7 +751,7 @@ void DownloadSession::send_next_request() {
this->pending_requests.clear();
this->on_request_complete();
} else {
this->current_request = stoull(input, nullptr, 16);
this->current_request = std::stoull(input, nullptr, 16);
this->done_requests.emplace(this->current_request);
this->pending_requests.erase(this->current_request);
}
+15 -17
View File
@@ -8,8 +8,6 @@
#include "PSOEncryption.hh"
#include "StaticGameData.hh"
using namespace std;
static constexpr uint8_t EP1 = EnemyTypeDefinition::Flag::VALID_EP1;
static constexpr uint8_t EP2 = EnemyTypeDefinition::Flag::VALID_EP2;
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 NONE = 0xFF;
static const vector<EnemyTypeDefinition> type_defs{
static const std::vector<EnemyTypeDefinition> type_defs{
// clang-format off
// 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},
@@ -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, 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::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_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, EP1 | BOSS, 0x2D, 0x2D, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DE_ROL_LE", "De Rol Le", "Dal Ral Lie"},
{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 Ra Lie (mine)"},
{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::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},
@@ -167,19 +165,19 @@ const char* phosg::name_for_enum<EnemyType>(EnemyType type) {
template <>
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()) {
for (const auto& def : type_defs) {
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);
}
const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
static array<vector<vector<EnemyType>>, 5> data;
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
static std::array<std::vector<std::vector<EnemyType>>, 5> data;
auto& ret = data.at(static_cast<size_t>(episode));
if (ret.empty()) {
for (const auto& def : type_defs) {
@@ -196,8 +194,8 @@ const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8
}
try {
return ret.at(rt_index);
} catch (const out_of_range&) {
static const vector<EnemyType> empty_vec;
} catch (const std::out_of_range&) {
static const std::vector<EnemyType> 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 bool cache_populated = false;
static array<vector<BPIndexCacheEntry>, 5> data;
static std::array<std::vector<BPIndexCacheEntry>, 5> data;
if (!cache_populated) {
cache_populated = true;
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) {
try {
return get_bp_index_cache_entry(episode, bp_index).stats;
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
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) {
try {
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;
}
}
@@ -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) {
try {
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;
}
}
@@ -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) {
try {
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;
}
}
+13 -15
View File
@@ -2,22 +2,20 @@
#include "Server.hh"
using namespace std;
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
// code. This is relevant for consistency of results when choosing a random card
// (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,
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,
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,
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,
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,
@@ -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) {
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},
{0x0019, /* 0x004A */ AssistEffect::RICH_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},
{0x00F6, /* 0x0002 */ AssistEffect::DICE_PLUS_1},
{0x00F7, /* 0x0003 */ AssistEffect::DICE_FEVER},
@@ -109,18 +107,18 @@ AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_nte) {
});
try {
return card_id_to_effect.at(card_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (!is_nte) {
try {
return card_id_to_effect_final_only.at(card_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
}
return AssistEffect::NONE;
}
AssistServer::AssistServer(shared_ptr<Server> server)
AssistServer::AssistServer(std::shared_ptr<Server> server)
: w_server(server),
assist_effects(AssistEffect::NONE),
num_assist_cards_set(0),
@@ -128,18 +126,18 @@ AssistServer::AssistServer(shared_ptr<Server> server)
active_assist_effects(AssistEffect::NONE),
num_active_assists(0) {}
shared_ptr<Server> AssistServer::server() {
std::shared_ptr<Server> AssistServer::server() {
auto s = this->w_server.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
shared_ptr<const Server> AssistServer::server() const {
std::shared_ptr<const Server> AssistServer::server() const {
auto s = this->w_server.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
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);
}
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 {
return this->server()->definition_for_card_id(card_id);
}
+32 -40
View File
@@ -5,8 +5,6 @@
#include "../CommandFormats.hh"
#include "../SendCommands.hh"
using namespace std;
namespace Episode3 {
void BattleRecord::PlayerEntry::print(FILE* stream) const {
@@ -41,7 +39,7 @@ BattleRecord::Event::Event(phosg::StringReader& r) {
this->data = r.read(r.get_u16l());
break;
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) {
case Event::Type::PLAYER_JOIN:
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]);
break;
@@ -75,13 +73,12 @@ void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
w.write(this->data);
break;
default:
throw logic_error("unknown event type");
throw std::logic_error("unknown event type");
}
}
void BattleRecord::Event::print(FILE* stream) const {
string time_str = phosg::format_time(this->timestamp);
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, time_str);
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, phosg::format_time(this->timestamp));
switch (this->type) {
case Type::PLAYER_JOIN:
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);
break;
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_end_timestamp(0) {}
BattleRecord::BattleRecord(const string& data)
: is_writable(false),
behavior_flags(0),
battle_start_timestamp(0),
battle_end_timestamp(0) {
BattleRecord::BattleRecord(const std::string& data)
: is_writable(false), behavior_flags(0), battle_start_timestamp(0), battle_end_timestamp(0) {
phosg::StringReader r(data);
uint64_t signature = r.get_u64l();
@@ -145,7 +139,7 @@ BattleRecord::BattleRecord(const string& data)
} else if (signature == this->SIGNATURE_V2) {
has_random_stream = true;
} else {
throw runtime_error("incorrect battle record signature");
throw std::runtime_error("incorrect battle record signature");
}
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;
w.put_u64l(this->SIGNATURE_V2);
w.put_u64l(this->battle_start_timestamp);
@@ -176,13 +170,13 @@ string BattleRecord::serialize() const {
void BattleRecord::add_player(
const PlayerLobbyDataDCGC& lobby_data,
const PlayerInventory& inventory,
const PlayerDispDataDCPCV3& disp,
const PlayerDispDataV123& disp,
uint32_t level) {
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) {
throw runtime_error("cannot add player during battle");
throw std::runtime_error("cannot add player during battle");
}
Event& ev = this->events.emplace_back();
ev.type = Event::Type::PLAYER_JOIN;
@@ -196,7 +190,7 @@ void BattleRecord::add_player(
void BattleRecord::delete_player(uint8_t client_id) {
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();
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) {
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();
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);
}
void BattleRecord::add_command(Event::Type type, string&& data) {
void BattleRecord::add_command(Event::Type type, std::string&& data) {
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();
ev.type = type;
@@ -224,10 +218,9 @@ void BattleRecord::add_command(Event::Type type, string&& data) {
ev.data = std::move(data);
}
void BattleRecord::add_chat_message(
uint32_t guild_card_number, string&& data) {
void BattleRecord::add_chat_message(uint32_t guild_card_number, std::string&& data) {
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();
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);
}
const string& BattleRecord::get_random_stream() const {
const std::string& BattleRecord::get_random_stream() const {
return this->random_stream;
}
@@ -259,7 +252,7 @@ bool BattleRecord::is_map_definition_event(const Event& ev) {
void BattleRecord::set_battle_start_timestamp() {
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();
@@ -271,30 +264,30 @@ void BattleRecord::set_battle_start_timestamp() {
for (auto& ev : this->events) {
if (ev.type == Event::Type::PLAYER_JOIN) {
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];
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_present[player.lobby_data.client_id] = true;
} else if (ev.type == Event::Type::PLAYER_LEAVE) {
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;
} 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)) {
num_map_events++;
}
}
deque<Event> new_events;
std::deque<Event> new_events;
// Generate the initial players event
Event initial_ev;
@@ -335,32 +328,31 @@ void BattleRecord::set_battle_end_timestamp() {
}
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",
this->is_writable ? "writable" : "read-only",
this->behavior_flags,
this->battle_start_timestamp,
start_str,
phosg::format_time(this->battle_start_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) {
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),
record(rec),
event_it(this->record->events.begin()),
play_start_timestamp(0),
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;
}
void BattleRecordPlayer::set_lobby(shared_ptr<Lobby> l) {
void BattleRecordPlayer::set_lobby(std::shared_ptr<Lobby> l) {
this->lobby = l;
}
@@ -406,7 +398,7 @@ asio::awaitable<void> BattleRecordPlayer::play_task() {
switch (ev.type) {
case BattleRecord::Event::Type::PLAYER_JOIN:
// 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:
send_player_leave_notification(l, ev.leaving_client_id);
break;
+2 -2
View File
@@ -22,7 +22,7 @@ public:
struct PlayerEntry {
PlayerLobbyDataDCGC lobby_data;
PlayerInventory inventory;
PlayerDispDataDCPCV3 disp;
PlayerDispDataV123 disp;
le_uint32_t level;
void print(FILE* stream) const;
@@ -85,7 +85,7 @@ public:
void add_player(
const PlayerLobbyDataDCGC& lobby_data,
const PlayerInventory& inventory,
const PlayerDispDataDCPCV3& disp,
const PlayerDispDataV123& disp,
uint32_t level);
void delete_player(uint8_t client_id);
void add_command(Event::Type type, const void* data, size_t size);
+52 -59
View File
@@ -3,11 +3,9 @@
#include "../CommandFormats.hh"
#include "Server.hh"
using namespace std;
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_player_state(server->get_player_state(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
// doesn't, it certainly isn't behavior that the player would expect, so we
// 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_def_entry = s->definition_for_card_id(ps->get_sc_card_id());
@@ -56,7 +54,7 @@ void Card::init() {
} else {
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 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->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();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
shared_ptr<const Server> Card::server() const {
std::shared_ptr<const Server> Card::server() const {
auto s = this->w_server.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
shared_ptr<PlayerState> Card::player_state() {
std::shared_ptr<PlayerState> Card::player_state() {
auto s = this->w_player_state.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
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();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
@@ -158,7 +156,7 @@ ssize_t Card::apply_abnormal_condition(
int16_t existing_cond_value = 0;
auto& cond = this->action_chain.conditions[cond_index];
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);
}
@@ -176,7 +174,7 @@ ssize_t Card::apply_abnormal_condition(
switch (eff.arg1.at(0)) {
case 'a': {
string s = eff.arg1.decode();
std::string s = eff.arg1.decode();
cond.a_arg_value = atoi(s.c_str() + 1);
break;
}
@@ -190,13 +188,12 @@ ssize_t Card::apply_abnormal_condition(
cond.remaining_turns = 102;
break;
case 't': {
string s = eff.arg1.decode();
std::string s = eff.arg1.decode();
cond.remaining_turns = atoi(s.c_str() + 1);
}
}
string cond_str = cond.str(s);
log.debug_f("wrote condition {} => {}", cond_index, cond_str);
log.debug_f("wrote condition {} => {}", cond_index, cond.str(s));
if (!is_nte) {
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) {
continue;
}
string cond_str = cond.str(s);
log.debug_f("sorted conditions: [{}] => {}", z, cond_str);
log.debug_f("sorted conditions: [{}] => {}", z, cond.str(s));
}
}
@@ -213,7 +209,7 @@ ssize_t Card::apply_abnormal_condition(
}
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* in_defense_power,
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)) {
case AssistEffect::POWERLESS_RAIN:
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;
case AssistEffect::BRAVE_WIND:
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;
case AssistEffect::SILENT_COLOSSEUM:
@@ -284,7 +280,7 @@ bool Card::check_card_flag(uint32_t flags) const {
void Card::commit_attack(
int16_t damage,
shared_ptr<Card> attacker_card,
std::shared_ptr<Card> attacker_card,
G_ApplyConditionEffect_Ep3_6xB4x06* cmd,
size_t strike_number,
int16_t* out_effective_damage) {
@@ -302,7 +298,7 @@ void Card::commit_attack(
auto eff = s->assist_server->get_active_assist_by_index(z);
if ((eff == AssistEffect::RANSOM) && (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
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;
effective_damage -= exp_amount;
if (!is_nte) {
@@ -326,7 +322,7 @@ void Card::commit_attack(
this->player_state()->stats.damage_taken += effective_damage;
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);
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) {
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;
}
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 ps = this->player_state();
@@ -490,7 +486,7 @@ int32_t Card::error_code_for_move_to_location(const Location& loc) const {
return 0;
}
void Card::execute_attack(shared_ptr<Card> attacker_card) {
void Card::execute_attack(std::shared_ptr<Card> attacker_card) {
if (!attacker_card) {
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) {
// Probably Resta
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();
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);
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;
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.ap = is_nte ? defense_power : min<int16_t>(defense_power, 99);
cmd.effect.current_hp = is_nte ? attack_ap : std::min<int16_t>(attack_ap, 99);
cmd.effect.ap = is_nte ? defense_power : std::min<int16_t>(defense_power, 99);
cmd.effect.tp = attack_tp;
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++) {
int16_t final_effective_damage = 0;
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 {
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);
}
shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
std::shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
return this->def_entry;
}
@@ -839,10 +835,10 @@ void Card::set_current_and_max_hp(int16_t hp) {
void Card::set_current_hp(
uint32_t new_hp, bool propagate_shared_hp, bool enforce_max_hp) {
if (!enforce_max_hp) {
new_hp = max<int16_t>(new_hp, 0);
this->max_hp = max<int16_t>(this->max_hp, new_hp);
new_hp = std::max<int16_t>(new_hp, 0);
this->max_hp = std::max<int16_t>(this->max_hp, new_hp);
} 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;
@@ -955,11 +951,11 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
}
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);
}
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);
}
@@ -1099,7 +1095,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
this->action_chain.chain.damage = is_nte
? (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) = {}",
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);
log.debug_f("applied action conditions (2)");
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);
}
} 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)) {
string chain_str = this->action_chain.str(s);
log.debug_f("result computed as {}", chain_str);
log.debug_f("result computed as {}", this->action_chain.str(s));
}
}
@@ -1208,20 +1203,19 @@ void Card::move_phase_before() {
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 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 (as) {
string as_str = as->str(s);
log.debug_f("as = {}", as_str);
log.debug_f("as = {}", as->str(s));
} else {
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->unknown_80236554(other_card, as)) {
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);
this->server()->card_special->unknown_8024A9D8(pa, 0xFFFF);
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) {
card->action_chain.set_action_subphase_from_card(this->shared_from_this());
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) {
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]);
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) {
sc_card->action_metadata.add_target_card_ref(this->card_ref);
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);
}
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 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())
@@ -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);
this->last_attack_preliminary_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;
}
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();
for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = s->player_states[client_id];
if (ps) {
shared_ptr<Card> card = ps->get_sc_card();
std::shared_ptr<Card> card = ps->get_sc_card();
if (card) {
card->execute_attack(attacker_card);
}
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) {
card->execute_attack(attacker_card);
}
@@ -1476,7 +1470,7 @@ void Card::apply_attack_result() {
temp_chain.chain.target_card_ref_count++;
} else if ((target_card->get_definition()->def.type == CardType::ITEM) && !this->action_chain.check_flag(0x02)) {
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++) {
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()) {
@@ -1555,12 +1549,11 @@ void Card::apply_attack_result() {
}
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
string as_str = as.str(s);
log.debug_f("as constructed as {}", as_str);
log.debug_f("as constructed as {}", as.str(s));
}
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) {
card->current_defense_power = card->action_metadata.attack_bonus;
if (!this->action_chain.check_flag(0x40)) {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -905,7 +905,7 @@ struct PlayerConfig {
/* 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
// 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;
/* 2350:21FC */
+6 -8
View File
@@ -2,8 +2,6 @@
#include "Server.hh"
using namespace std;
namespace Episode3 {
NameEntry::NameEntry() {
@@ -198,7 +196,7 @@ void DeckState::redraw_initial_hand(bool is_nte) {
auto s = this->server.lock();
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).
@@ -260,7 +258,7 @@ void DeckState::shuffle() {
if (this->shuffle_enabled) {
auto s = this->server.lock();
if (!s) {
throw runtime_error("server is missing");
throw std::runtime_error("server is missing");
}
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");
for (size_t z = 0; z < 31; z++) {
const auto& e = this->entries[z];
shared_ptr<const CardIndex::CardEntry> ce;
std::shared_ptr<const CardIndex::CardEntry> ce;
if (card_index) {
try {
ce = card_index->definition_for_id(e.card_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
}
if (ce) {
string name = ce->def.en_name.decode(Language::ENGLISH);
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 {
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));
-2
View File
@@ -1,7 +1,5 @@
#include "MapState.hh"
using namespace std;
namespace Episode3 {
MapState::MapState() {
+47 -53
View File
@@ -2,11 +2,9 @@
#include "Server.hh"
using namespace std;
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),
client_id(client_id),
num_hand_redraws_allowed(1),
@@ -46,10 +44,10 @@ void PlayerState::init() {
if (s->player_states.at(this->client_id).get() != this) {
// Note: The original code handles this, but we don't. This appears not to
// 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) {
this->deck_state->disable_shuffle();
}
@@ -62,7 +60,7 @@ void PlayerState::init() {
this->team_id = s->deck_entries[this->client_id]->team_id;
auto sc_ce = s->definition_for_card_ref(this->sc_card_ref);
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) {
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
// uint32_t). This probably breaks some things later on, so we instead
// 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->card_short_statuses = make_shared<parray<CardShortStatus, 0x10>>();
this->set_card_action_chains = make_shared<parray<ActionChainWithConds, 9>>();
this->set_card_action_metadatas = make_shared<parray<ActionMetadata, 9>>();
this->hand_and_equip = std::make_shared<HandAndEquipState>();
this->card_short_statuses = std::make_shared<parray<CardShortStatus, 0x10>>();
this->set_card_action_chains = std::make_shared<parray<ActionChainWithConds, 9>>();
this->set_card_action_metadatas = std::make_shared<parray<ActionMetadata, 9>>();
this->hand_and_equip->clear_FF();
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->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->draw_initial_hand();
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);
}
shared_ptr<Server> PlayerState::server() {
std::shared_ptr<Server> PlayerState::server() {
auto s = this->w_server.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
shared_ptr<const Server> PlayerState::server() const {
std::shared_ptr<const Server> PlayerState::server() const {
auto s = this->w_server.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
@@ -151,7 +149,7 @@ bool PlayerState::draw_cards_allowed() const {
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();
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: {
uint16_t total_cost = 0;
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) {
auto ce = card->get_definition();
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) {
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();
if (!is_nte) {
s->send_6xB4x05();
@@ -353,7 +351,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
if (is_nte
? (other_ps->assist_remaining_turns != 90 && other_ps->assist_remaining_turns != 99)
: (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++) {
@@ -369,7 +367,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
cond.remaining_turns <<= 1;
}
} 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++) {
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 {
auto s = this->server();
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++) {
auto other_ps = s->player_states[client_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];
}
shared_ptr<DeckState> PlayerState::get_deck() {
std::shared_ptr<DeckState> PlayerState::get_deck() {
return this->deck_state;
}
@@ -871,11 +868,11 @@ uint16_t PlayerState::get_sc_card_id() const {
return this->sc_card_id;
}
shared_ptr<Card> PlayerState::get_sc_card() {
std::shared_ptr<Card> PlayerState::get_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;
}
@@ -887,11 +884,11 @@ CardType PlayerState::get_sc_card_type() const {
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;
}
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;
}
@@ -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) {
auto s = this->server();
shared_ptr<Card> card;
std::shared_ptr<Card> card;
if (card_index == 0) {
card = this->sc_card;
} else {
@@ -1010,7 +1007,7 @@ void PlayerState::move_null_hand_refs_to_end() {
void PlayerState::on_cards_destroyed() {
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++) {
auto card = this->set_cards[z];
if (!card || !(card->card_flags & 2)) {
@@ -1333,7 +1330,7 @@ bool PlayerState::set_card_from_hand(
}
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?
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];
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};
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;
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) {
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) {
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) {
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 {
@@ -1571,7 +1568,7 @@ void PlayerState::set_random_assist_card_from_hand_for_free() {
auto s = this->server();
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++) {
uint16_t card_ref = this->card_refs[hand_index];
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 {
auto s = this->server();
vector<uint16_t> ret;
std::vector<uint16_t> ret;
for (size_t client_id = 0; client_id < 4; client_id++) {
auto other_ps = s->player_states[client_id];
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;
case AssistEffect::ATK_DICE_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();
break;
case AssistEffect::SKIP_TURN:
@@ -1756,8 +1753,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
size_t z = 0;
do {
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 {}", ref_str);
log.debug_f("on action card ref {}", s->debug_str_for_card_ref(pa.action_card_refs[z]));
}
card->unknown_80237A90(pa, 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]);
if (target_card) {
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 {}", ref_str);
log.debug_f("on target card ref {}", s->debug_str_for_card_ref(pa.target_card_refs[z]));
}
target_card->unknown_802379DC(pa);
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++) {
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", ref_str);
log.debug_f("discarding {} from hand", s->debug_str_for_card_ref(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();
}
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) {
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->def_points += s->team_dice_bonus[this->team_id];
this->atk_points = clamp<uint8_t>(this->atk_points, 1, 9);
this->def_points = clamp<uint8_t>(this->def_points, 1, 9);
this->atk_points = std::clamp<uint8_t>(this->atk_points, 1, 9);
this->def_points = std::clamp<uint8_t>(this->def_points, 1, 9);
if (!s->options.is_nte()) {
this->atk_bonuses = this->atk_points - atk_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();
}
@@ -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 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->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();
}
+16 -18
View File
@@ -2,8 +2,6 @@
#include "Server.hh"
using namespace std;
namespace Episode3 {
Condition::Condition() {
@@ -61,7 +59,7 @@ void Condition::clear_FF() {
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(
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
"def_eff_index={}, ref={}, value={}, giver_ref={} "
@@ -98,7 +96,7 @@ void EffectResult::clear() {
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(
"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),
@@ -130,7 +128,7 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const {
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(
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, u1={:04X}, max_hp={}, u2={}]",
s->debug_str_for_card_ref(this->card_ref),
@@ -178,7 +176,7 @@ void ActionState::clear() {
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(
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, def_ref={}, target_refs={}, action_refs={}, orig_attacker_ref={}]",
this->client_id,
@@ -222,7 +220,7 @@ bool ActionChain::operator!=(const ActionChain& other) const {
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(
"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={}, "
@@ -309,8 +307,8 @@ bool ActionChainWithConds::operator!=(const ActionChainWithConds& other) const {
return !this->operator==(other);
}
std::string ActionChainWithConds::str(shared_ptr<const Server> s) const {
string ret = "ActionChainWithConds[chain=";
std::string ActionChainWithConds::str(std::shared_ptr<const Server> s) const {
std::string ret = "ActionChainWithConds[chain=";
ret += this->chain.str(s);
ret += ", conds=[";
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;
}
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) {
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;
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];
@@ -437,7 +435,7 @@ bool ActionChainWithConds::get_condition_value(
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();
}
@@ -545,7 +543,7 @@ bool ActionMetadata::operator!=(const ActionMetadata& other) const {
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(
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, subphase={}, def_power={}, def_bonus={}, "
"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(
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)) {
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;
@@ -634,7 +632,7 @@ HandAndEquipState::HandAndEquipState() {
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(
"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={}, "
@@ -777,7 +775,7 @@ uint8_t PlayerBattleStats::rank_for_score(float score) {
const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
if (rank >= RANK_THRESHOLD_COUNT + 1) {
throw invalid_argument("invalid rank");
throw std::invalid_argument("invalid rank");
}
return RANK_NAMES[rank];
}
@@ -842,12 +840,12 @@ static bool is_card_within_range(
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 Location& loc,
const parray<CardShortStatus, 0x10>& short_statuses,
phosg::PrefixedLogger* log) {
vector<uint16_t> ret;
std::vector<uint16_t> ret;
if (is_card_within_range(range, loc, short_statuses[0], log)) {
if (log) {
log->debug_f("get_card_refs_within_range: sc card @{:04X} within range", short_statuses[0].card_ref);
+57 -64
View File
@@ -5,20 +5,17 @@
#include "DataIndexes.hh"
#include "Server.hh"
using namespace std;
namespace Episode3 {
void compute_effective_range(
parray<uint8_t, 9 * 9>& ret,
shared_ptr<const CardIndex> card_index,
std::shared_ptr<const CardIndex> card_index,
uint16_t card_id,
const Location& loc,
shared_ptr<const MapAndRulesState> map_and_rules,
std::shared_ptr<const MapAndRulesState> map_and_rules,
phosg::PrefixedLogger* log) {
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:");
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
range_def[3] = 0x00000100;
} else {
shared_ptr<const CardIndex::CardEntry> ce;
std::shared_ptr<const CardIndex::CardEntry> ce;
try {
ce = card_index->definition_for_id(card_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
return;
}
for (size_t z = 0; z < 6; z++) {
@@ -95,7 +92,7 @@ void compute_effective_range(
up_y = 9 - y - 1;
break;
default:
throw logic_error("invalid direction");
throw std::logic_error("invalid direction");
}
ret[y * 9 + x] = decoded_range[up_y * 9 + up_x];
if (log) {
@@ -117,9 +114,9 @@ void compute_effective_range(
}
bool card_linkage_is_valid(
shared_ptr<const CardIndex::CardEntry> right_ce,
shared_ptr<const CardIndex::CardEntry> left_ce,
shared_ptr<const CardIndex::CardEntry> sc_ce,
std::shared_ptr<const CardIndex::CardEntry> right_ce,
std::shared_ptr<const CardIndex::CardEntry> left_ce,
std::shared_ptr<const CardIndex::CardEntry> sc_ce,
bool has_permission_effect) {
if (!right_ce) {
return false;
@@ -173,31 +170,27 @@ bool card_linkage_is_valid(
return false;
}
RulerServer::RulerServer(shared_ptr<Server> server)
: w_server(server),
team_id_for_client_id(0xFF),
error_code1(0),
error_code2(0),
error_code3(0) {}
RulerServer::RulerServer(std::shared_ptr<Server> server)
: w_server(server), 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();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
shared_ptr<const Server> RulerServer::server() const {
std::shared_ptr<const Server> RulerServer::server() const {
auto s = this->w_server.lock();
if (!s) {
throw runtime_error("server is deleted");
throw std::runtime_error("server is deleted");
}
return s;
}
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 {
@@ -398,7 +391,7 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
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);
if (chain) {
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) {
@@ -767,7 +760,7 @@ bool RulerServer::check_move_path_and_get_cost(
if (max_dist < 1) {
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);
if (!short_status) {
@@ -1030,7 +1023,7 @@ bool RulerServer::check_usability_or_condition_apply(
}
break;
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
0x0002, // Kranz
0x0003, // Ino'lis
@@ -1059,7 +1052,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0113, // Teifu
0x02AA, // H-HUmar
@@ -1070,7 +1063,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0004, // Sil'fer
0x0006, // Kylria
@@ -1094,7 +1087,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0003, // Ino'lis
0x0004, // Sil'fer
@@ -1117,7 +1110,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0113, // Teifu
0x02AC, // H-HUcast
@@ -1128,7 +1121,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0003, // Ino'lis
0x0110, // Saligun
@@ -1148,7 +1141,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0002, // Kranz
0x0005, // Guykild
@@ -1166,7 +1159,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x02AB, // H-HUnewearl
0x02CF, // H-HUnewearl
@@ -1174,7 +1167,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0005, // Guykild
0x02AE, // H-RAmar
@@ -1185,7 +1178,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0114, // Stella
0x02AF, // H-RAmarl
@@ -1196,7 +1189,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0006, // Kylria
0x0112, // Viviana
@@ -1213,7 +1206,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0002, // Kranz
0x0004, // Sil'fer
@@ -1230,7 +1223,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0114, // Stella
0x02B0, // H-RAcast
@@ -1241,7 +1234,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0112, // Viviana
0x02B3, // H-FOmarl
@@ -1252,7 +1245,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
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
0x0004, // Sil'fer
0x0006, // Kylria
@@ -1269,7 +1262,7 @@ bool RulerServer::check_usability_or_condition_apply(
return ret && card_ids.count(card_id2);
}
case CriterionCode::HUNTER_ANDROID_SC: {
static const unordered_set<uint16_t> card_ids = {
static const std::unordered_set<uint16_t> card_ids = {
0x0005, // Guykild
0x0110, // Saligun
0x0113, // Teifu
@@ -1433,14 +1426,14 @@ uint16_t RulerServer::compute_attack_or_defense_costs(
final_cost = 0;
}
} 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) {
*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(
@@ -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 {
auto ce = this->definition_for_card_ref(pa.action_card_refs[0]);
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);
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;
}
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);
if (card_id == 0xFFFF) {
return nullptr;
@@ -1841,7 +1834,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
return -0x7E;
}
} 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 (loc->x < summon_area_loc.x) {
return -0x7E;
@@ -1860,7 +1853,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
return -0x7E;
}
} 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 (loc->y < summon_area_loc.y) {
return -0x7E;
@@ -1974,7 +1967,7 @@ bool RulerServer::flood_fill_move_path(
Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)};
for (size_t dir_index = 0; dir_index < 3; dir_index++) {
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(
chain,
@@ -2017,7 +2010,7 @@ uint16_t RulerServer::get_ally_sc_card_ref(uint16_t card_ref) const {
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);
}
@@ -2130,11 +2123,11 @@ bool RulerServer::get_creature_summon_area(uint8_t client_id, Location* out_loc,
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;
}
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;
}
@@ -2171,7 +2164,7 @@ ssize_t RulerServer::get_path_cost(
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 {
@@ -2440,9 +2433,9 @@ bool RulerServer::is_defense_valid(const ActionState& pa) {
}
void RulerServer::link_objects(
shared_ptr<MapAndRulesState> map_and_rules,
shared_ptr<StateFlags> state_flags,
shared_ptr<AssistServer> assist_server) {
std::shared_ptr<MapAndRulesState> map_and_rules,
std::shared_ptr<StateFlags> state_flags,
std::shared_ptr<AssistServer> assist_server) {
this->map_and_rules = map_and_rules;
this->state_flags = state_flags;
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)) {
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);
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(
uint8_t client_id,
shared_ptr<HandAndEquipState> hes,
shared_ptr<parray<CardShortStatus, 0x10>> short_statuses,
shared_ptr<DeckEntry> deck_entry,
shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas) {
std::shared_ptr<HandAndEquipState> hes,
std::shared_ptr<parray<CardShortStatus, 0x10>> short_statuses,
std::shared_ptr<DeckEntry> deck_entry,
std::shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
std::shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas) {
this->hand_and_equip_states[client_id] = hes;
this->short_statuses[client_id] = short_statuses;
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
ret = is_nte ? (ret << 1) : (ret + (ret >> 1));
} 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) {
ret++;
}
+117 -119
View File
@@ -7,11 +7,9 @@
#include "../Revision.hh"
#include "../SendCommands.hh"
using namespace std;
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"
// 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";
@@ -27,7 +25,7 @@ void Server::PresenceEntry::clear() {
this->is_cpu_player = 0;
}
Server::Server(shared_ptr<Lobby> lobby, Options&& options)
Server::Server(std::shared_ptr<Lobby> lobby, Options&& options)
: lobby(lobby),
battle_record(lobby ? lobby->battle_record : nullptr),
has_lobby(lobby != nullptr),
@@ -81,7 +79,7 @@ Server::Server(shared_ptr<Lobby> lobby, Options&& options)
Server::~Server() noexcept(false) {
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();
}
@@ -89,31 +87,31 @@ Server::~Server() noexcept(false) {
void Server::init() {
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->overlay_state.clear();
for (size_t z = 0; z < 4; z++) {
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_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
// 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.
// 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->update_battle_state_flags_and_send_6xB4x03_if_needed();
this->assist_server = make_shared<AssistServer>(this->shared_from_this());
this->ruler_server = make_shared<RulerServer>(this->shared_from_this());
this->assist_server = std::make_shared<AssistServer>(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->send_6xB4x46();
@@ -135,7 +133,7 @@ Server::StackLogger::StackLogger(StackLogger&& other)
: PrefixedLogger(std::move(other)),
server(other.server) {
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;
}
@@ -144,7 +142,7 @@ Server::StackLogger& Server::StackLogger::operator=(StackLogger&& other) {
this->PrefixedLogger::operator=(std::move(other));
this->server = other.server;
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;
return *this;
@@ -152,7 +150,7 @@ Server::StackLogger& Server::StackLogger::operator=(StackLogger&& other) {
Server::StackLogger::~StackLogger() noexcept(false) {
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();
}
@@ -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);
if (ce) {
string name = ce->def.en_name.decode();
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, name);
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, ce->def.en_name.decode());
} else {
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);
if (ce) {
string name = ce->def.en_name.decode();
return std::format("#{:04X} ({})", card_id, name);
return std::format("#{:04X} ({})", card_id, ce->def.en_name.decode());
} else {
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]) {
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]) {
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++) {
if (!team_win_flag_counts[z]) {
continue;
}
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;
}
@@ -236,10 +232,10 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
if (this->has_lobby) {
auto l = this->lobby.lock();
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 &&
!this->options.is_nte() &&
!(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 {
// 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.
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
@@ -275,12 +271,13 @@ void Server::send_6xB4x46() const {
cmd.date_str1.encode(
std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()),
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);
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);
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 {
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(
"Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
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);
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() {
@@ -396,7 +395,7 @@ bool Server::advance_battle_phase() {
this->dice_phase_before();
break;
default:
throw logic_error("invalid battle phase");
throw std::logic_error("invalid battle phase");
}
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 {
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;
}
}
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) {
return nullptr;
}
@@ -447,7 +446,7 @@ shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
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);
}
@@ -584,7 +583,7 @@ bool Server::check_for_battle_end() {
void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
auto ps = this->player_states.at(client_id);
if (!ps) {
throw runtime_error("player does not exist");
throw std::runtime_error("player does not exist");
}
if (card_id == 0xFFFF) {
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) {
auto ps = this->player_states.at(client_id);
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
// 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++) {
if (!ps->set_cards[set_index]) {
continue;
@@ -681,7 +680,7 @@ void Server::compute_all_map_occupied_bits() {
}
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);
}
@@ -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 {
return this->options.card_index->definition_for_id(card_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
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[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) {
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;
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) {
auto log = this->log_stack("enqueue_attack_or_defense: ");
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);
}
@@ -977,8 +976,8 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
size_t attack_index = this->num_pending_attacks++;
this->pending_attacks[attack_index] = *pa;
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 {}: {}", attack_index, pa_str);
log.debug_f("set pending attack {}: {}",
attack_index, this->pending_attacks[attack_index].str(this->shared_from_this()));
}
ps->set_action_cards_for_action_state(*pa);
log.debug_f("set action cards");
@@ -1013,14 +1012,14 @@ uint8_t Server::get_current_team_turn() const {
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) {
return nullptr;
}
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) {
return nullptr;
}
@@ -1094,19 +1093,19 @@ void Server::move_phase_after() {
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
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
std::vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
// 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
vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
std::vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
// 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
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()) {
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;
uint8_t ally_cost = 0;
uint8_t setter_client_id = 0xFF;
shared_ptr<PlayerState> setter_ps = nullptr;
std::shared_ptr<PlayerState> setter_ps = nullptr;
cmd.card_ref = 0xFFFF;
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]);
@@ -1509,7 +1508,7 @@ void Server::setup_and_start_battle() {
this->name_entries[z].clear();
}
} 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();
}
}
@@ -1523,7 +1522,7 @@ void Server::setup_and_start_battle() {
}
auto card = ps->get_sc_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;
}
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},
{0x0C, &Server::handle_CAx0C_end_redraw_initial_hand_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},
});
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);
size_t expected_size = header.size * 4;
if (expected_size < data.size()) {
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()));
}
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;
try {
handler = this->subcommand_handlers.at(header.subsubcommand);
} catch (const out_of_range&) {
throw runtime_error("unknown CAx subsubcommand");
} catch (const std::out_of_range&) {
throw std::runtime_error("unknown CAx subsubcommand");
}
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) {
(this->*handler)(sender_c, data);
} else {
string unmasked_data = data;
std::string unmasked_data = data;
set_mask_for_ep3_game_command(unmasked_data.data(), unmasked_data.size(), 0);
(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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "HAND READY");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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);
}
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
if (in_cmd.client_id >= 4) {
throw runtime_error("invalid client ID");
throw std::runtime_error("invalid client ID");
}
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>
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);
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()) {
this->handle_CAx13_update_map_during_setup_t<G_SetMapState_Ep3NTE_CAx13>(c, data);
} 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);
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) {
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)) {
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);
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->update_registration_phase();
// 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);
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);
}
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);
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);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "END BATTLE");
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
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;
@@ -2369,13 +2368,13 @@ void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& dat
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);
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.
}
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);
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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "CHOOSE ORDER");
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];
@@ -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);
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.
}
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);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "MAP LIST");
auto l = this->lobby.lock();
if (!l) {
throw runtime_error("lobby is deleted");
throw std::runtime_error("lobby is deleted");
}
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 {
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();
if (l) {
vector<string> map_commands_by_language;
auto send_to_client = [&](shared_ptr<Client> c) -> void {
std::vector<std::string> map_commands_by_language;
auto send_to_client = [&](std::shared_ptr<Client> c) -> void {
if (!c) {
return;
}
@@ -2545,7 +2544,7 @@ void Server::send_6xB6x41_to_all_clients() const {
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
// 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()) {
this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data));
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);
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)) {
@@ -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);
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
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);
@@ -2585,7 +2584,7 @@ void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
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);
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;
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) {
// 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];
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
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);
log.debug_f("as: {}", as.str(this->shared_from_this()));
}
if (is_nte) {
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);
}
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);
log.debug_f("as after target replacement: {}", as.str(this->shared_from_this()));
}
if (this->any_target_exists_for_attack(as)) {
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++) {
auto card = ps->get_set_card(set_index);
if (card && !(card->card_flags & 2)) {
max_hp = max<int16_t>(max_hp, card->get_current_hp());
min_hp = min<int16_t>(min_hp, card->get_current_hp());
max_hp = std::max<int16_t>(max_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;
}
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++) {
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()) {
@@ -2915,7 +2912,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
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++) {
auto ps = this->get_player_state(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;
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++) {
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);
@@ -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.
vector<shared_ptr<Card>> ret;
std::vector<std::shared_ptr<Card>> ret;
for (auto const_card : cards) {
auto mutable_card = this->card_for_set_card_ref(const_card->get_card_ref());
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);
}
+1 -1
View File
@@ -37,7 +37,7 @@ namespace Episode3 {
// RulerServer.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
// - - RulerServer
// - - - AssistServer
+100 -116
View File
@@ -7,23 +7,18 @@
#include "../SendCommands.hh"
#include "../ServerState.hh"
using namespace std;
namespace Episode3 {
Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const string& player_name)
: account_id(account_id),
player_name(player_name) {}
Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const std::string& player_name)
: account_id(account_id), 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),
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(
shared_ptr<const COMDeckDefinition> com_deck)
: account_id(0),
com_deck(com_deck) {}
Tournament::PlayerEntry::PlayerEntry(std::shared_ptr<const COMDeckDefinition> com_deck)
: account_id(0), com_deck(com_deck) {}
bool Tournament::PlayerEntry::is_com() const {
return (this->com_deck != nullptr);
@@ -33,8 +28,7 @@ bool Tournament::PlayerEntry::is_human() const {
return (this->account_id != 0);
}
Tournament::Team::Team(
shared_ptr<Tournament> tournament, size_t index, size_t max_players)
Tournament::Team::Team(std::shared_ptr<Tournament> tournament, size_t index, size_t max_players)
: tournament(tournament),
index(index),
max_players(max_players),
@@ -43,7 +37,7 @@ Tournament::Team::Team(
num_rounds_cleared(0),
is_active(true) {}
string Tournament::Team::str() const {
std::string Tournament::Team::str() const {
size_t num_human_players = 0;
size_t num_com_players = 0;
for (const auto& player : this->players) {
@@ -51,7 +45,7 @@ string Tournament::Team::str() const {
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",
num_human_players, num_com_players, this->max_players, this->name,
this->password, this->num_rounds_cleared);
@@ -67,26 +61,27 @@ string Tournament::Team::str() const {
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) {
throw runtime_error("team is full");
throw std::runtime_error("team is full");
}
if (!this->name.empty() && (password != this->password)) {
throw runtime_error("incorrect password");
throw std::runtime_error("incorrect password");
}
auto tournament = this->tournament.lock();
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) {
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) {
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
for (auto match : tournament->pending_matches) {
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) {
match->set_winner_team(match->preceding_b->winner_team);
@@ -139,7 +134,7 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
} else {
// If the tournament has not started yet, just remove the player from the team
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(
shared_ptr<Tournament> tournament, shared_ptr<Match> preceding_a, shared_ptr<Match> preceding_b)
: tournament(tournament),
preceding_a(preceding_a),
preceding_b(preceding_b),
winner_team(nullptr),
round_num(0) {
std::shared_ptr<Tournament> tournament, std::shared_ptr<Match> preceding_a, std::shared_ptr<Match> preceding_b)
: tournament(tournament), 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) {
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;
}
Tournament::Match::Match(shared_ptr<Tournament> tournament, shared_ptr<Team> winner_team)
: tournament(tournament),
preceding_a(nullptr),
preceding_b(nullptr),
winner_team(winner_team),
round_num(0) {}
Tournament::Match::Match(std::shared_ptr<Tournament> tournament, std::shared_ptr<Team> winner_team)
: tournament(tournament), preceding_a(nullptr), preceding_b(nullptr), winner_team(winner_team), round_num(0) {}
string Tournament::Match::str() const {
string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
std::string Tournament::Match::str() const {
std::string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
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) {
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)) {
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;
@@ -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->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) {
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) {
return this->preceding_b->winner_team;
} else if (team == this->preceding_b->winner_team) {
return this->preceding_a->winner_team;
} else {
throw logic_error("team is not registered for this match");
throw std::logic_error("team is not registered for this match");
}
}
Tournament::Tournament(
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
const string& name,
shared_ptr<const MapIndex::Map> map,
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
const std::string& name,
std::shared_ptr<const MapIndex::Map> map,
const Rules& rules,
size_t num_teams,
uint8_t flags)
@@ -317,18 +304,20 @@ Tournament::Tournament(
current_state(State::REGISTRATION),
menu_item_id(0xFFFFFFFF) {
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) {
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)) {
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(
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"))),
map_index(map_index),
com_deck_index(com_deck_index),
@@ -336,7 +325,7 @@ Tournament::Tournament(
current_state(State::REGISTRATION) {}
void Tournament::init() {
vector<size_t> team_index_to_rounds_cleared;
std::vector<size_t> team_index_to_rounds_cleared;
bool is_registration_complete;
if (!this->source_json.is_null()) {
@@ -350,7 +339,7 @@ void Tournament::init() {
is_registration_complete = this->source_json.get_bool("is_registration_complete");
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")));
team->name = team_json->get_string("name");
team->password = team_json->get_string("password");
@@ -367,7 +356,7 @@ void Tournament::init() {
} else if (player_json->is_string()) {
team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
} else {
throw runtime_error("invalid player spec");
throw std::runtime_error("invalid player spec");
}
}
}
@@ -378,7 +367,7 @@ void Tournament::init() {
} else {
// Create empty 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);
}
is_registration_complete = false;
@@ -390,12 +379,12 @@ void Tournament::init() {
this->create_bracket_matches();
// 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) {
match_queue.emplace(match->following.lock());
}
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
@@ -406,12 +395,12 @@ void Tournament::init() {
match_queue.erase(match_it);
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& b_rounds_cleared = team_index_to_rounds_cleared[match->preceding_b->winner_team->index];
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) {
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()) {
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
@@ -449,19 +438,19 @@ void Tournament::init() {
void Tournament::create_bracket_matches() {
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) {
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)) {
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
this->zero_round_matches.clear();
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);
if (this->current_state == State::REGISTRATION) {
this->pending_matches.emplace(m);
@@ -469,11 +458,11 @@ void Tournament::create_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) {
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) {
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 + 1]->following = 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) {
return nullptr;
}
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) {
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;
}
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) {
return nullptr;
}
for (auto match : this->pending_matches) {
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)) {
return match;
@@ -544,11 +533,11 @@ shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> t
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;
}
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)) {
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;
}
void Tournament::start() {
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);
@@ -584,7 +573,7 @@ void Tournament::start() {
}
}
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)) {
@@ -642,11 +631,11 @@ void Tournament::start() {
}
for (const auto& player : t->players) {
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()) {
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 (has_com_teams || !t->players.empty()) {
@@ -687,10 +676,10 @@ void Tournament::send_all_state_updates_on_deletion() const {
}
}
string Tournament::bracket_str() const {
string ret = std::format("Tournament \"{}\"\n", this->name);
std::string Tournament::bracket_str() const {
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 += m->str();
if (this->pending_matches.count(m)) {
@@ -707,13 +696,12 @@ string Tournament::bracket_str() const {
auto en_vm = this->map->version(Language::ENGLISH);
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);
} else {
ret += std::format(" Map: {:08X}\n", this->map->map_number);
}
string rules_str = this->rules.str();
ret += std::format(" Rules: {}\n", rules_str);
ret += std::format(" Rules: {}\n", this->rules.str());
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(" 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) {
ret += " Teams:\n";
for (const auto& team : this->teams) {
string team_str = team->str();
ret += std::format(" {}\n", team_str);
ret += std::format(" {}\n", team->str());
}
} else {
ret += " Pending matches:\n";
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(
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
const string& state_filename,
std::shared_ptr<const MapIndex> map_index,
std::shared_ptr<const COMDeckIndex> com_deck_index,
const std::string& state_filename,
bool skip_load_state)
: map_index(map_index),
com_deck_index(com_deck_index),
state_filename(state_filename) {
: map_index(map_index), com_deck_index(com_deck_index), state_filename(state_filename) {
if (this->state_filename.empty() || skip_load_state) {
return;
}
@@ -775,14 +759,14 @@ TournamentIndex::TournamentIndex(
if (json.is_list()) {
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()) {
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();
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());
this->menu_item_id_to_tournament.emplace_back(tourn);
@@ -790,20 +774,20 @@ TournamentIndex::TournamentIndex(
}
} else if (json.is_dict()) {
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()) {
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();
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
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());
this->menu_item_id_to_tournament.emplace_back(tourn);
}
} 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));
}
shared_ptr<Tournament> TournamentIndex::create_tournament(
const string& name,
shared_ptr<const MapIndex::Map> map,
std::shared_ptr<Tournament> TournamentIndex::create_tournament(
const std::string& name,
std::shared_ptr<const MapIndex::Map> map,
const Rules& rules,
size_t num_teams,
uint8_t flags) {
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();
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;
@@ -852,7 +836,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
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);
if (it == this->name_to_tournament.end()) {
return false;
@@ -869,7 +853,7 @@ bool TournamentIndex::delete_tournament(const string& name) {
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) {
const auto& tourn = it.second;
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;
}
void TournamentIndex::link_client(shared_ptr<Client> c) {
void TournamentIndex::link_client(std::shared_ptr<Client> c) {
if (!is_ep3(c->version())) {
return;
}
@@ -898,7 +882,7 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
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 {
c->ep3_tournament_team.reset();
if (c->version() == Version::GC_EP3) {
-89
View File
@@ -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;
}
}
-124
View File
@@ -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
View File
@@ -7,8 +7,6 @@
#include "Text.hh"
#include "Types.hh"
using namespace std;
template <bool BE>
struct GSLHeaderEntryT {
pstring<TextEncoding::ASCII, 0x20> filename;
@@ -30,13 +28,13 @@ void GSLArchive::load_t() {
}
uint64_t offset = static_cast<uint64_t>(entry.offset) * 0x800;
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});
}
}
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) {
this->load_t<true>();
} 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;
}
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 {
const auto& entry = this->entries.at(name);
return make_pair(this->data->data() + entry.offset, entry.size);
} catch (const out_of_range&) {
throw out_of_range("GSL does not contain file: " + name);
return std::make_pair(this->data->data() + entry.offset, entry.size);
} catch (const std::out_of_range&) {
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 {
const auto& entry = this->entries.at(name);
return this->data->substr(entry.offset, entry.size);
} catch (const out_of_range&) {
throw out_of_range("GSL does not contain file: " + name);
} catch (const std::out_of_range&) {
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 {
const auto& entry = this->entries.at(name);
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
} catch (const out_of_range&) {
throw out_of_range("GSL does not contain file: " + name);
} catch (const std::out_of_range&) {
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);
}
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;
// Make sure there's enough space for a blank header entry before any file's data pages begin
+25 -27
View File
@@ -18,14 +18,11 @@
#include "PSOProtocol.hh"
#include "ReceiveCommands.hh"
using namespace std;
using namespace std::placeholders;
GameServer::GameServer(shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
GameServer::GameServer(std::shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
void GameServer::listen(
const std::string& name,
const string& addr,
const std::string& addr,
uint16_t port,
Version version,
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);
auto sock = make_shared<GameServerSocket>();
auto sock = std::make_shared<GameServerSocket>();
sock->name = name;
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
sock->version = version;
@@ -42,8 +39,8 @@ void GameServer::listen(
this->add_socket(std::move(sock));
}
shared_ptr<Client> GameServer::connect_channel(shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state) {
auto c = make_shared<Client>(this->shared_from_this(), ch, initial_state);
std::shared_ptr<Client> GameServer::connect_channel(std::shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state) {
auto c = std::make_shared<Client>(this->shared_from_this(), ch, initial_state);
c->listener_port = port;
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;
}
shared_ptr<Client> GameServer::get_client() const {
std::shared_ptr<Client> GameServer::get_client() const {
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) {
throw runtime_error("multiple clients on game server");
throw std::runtime_error("multiple clients on game server");
}
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_dec = -1;
try {
account_id_dec = stoul(ident, nullptr, 10);
} catch (const invalid_argument&) {
} catch (const std::invalid_argument&) {
}
try {
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
// pretty rare.
vector<shared_ptr<Client>> results;
std::vector<std::shared_ptr<Client>> results;
for (const auto& c : this->clients) {
if (c->login && c->login->account->account_id == account_id_hex) {
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);
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);
continue;
}
@@ -115,8 +112,8 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
return results;
}
shared_ptr<Client> GameServer::create_client(
shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
std::shared_ptr<Client> GameServer::create_client(
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());
if (this->state->banned_ipv4_ranges->check(addr)) {
if (client_sock.is_open()) {
@@ -127,7 +124,7 @@ shared_ptr<Client> GameServer::create_client(
auto channel = SocketChannel::create(
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,
Language::ENGLISH,
"",
@@ -135,28 +132,29 @@ shared_ptr<Client> GameServer::create_client(
phosg::TerminalFormat::FG_GREEN,
this->state->censor_credentials,
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();
this->log.info_f("Client connected: C-{:X} via {}", c->id, listen_sock->name);
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 {
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());
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()));
try {
co_await on_connect(c);
} catch (const exception& e) {
} catch (const std::exception& e) {
this->log.warning_f("Error in client initialization: {}", e.what());
c->channel->disconnect();
}
@@ -183,17 +181,17 @@ asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> c) {
try {
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());
}
// 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.
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) {
try {
h_it.second();
} catch (const exception& e) {
} catch (const std::exception& e) {
c->log.warning_f("Disconnect hook {} failed: {}", h_it.first, e.what());
}
}
+84 -84
View File
@@ -15,9 +15,7 @@
#include "Server.hh"
#include "ShellCommands.hh"
using namespace std;
HTTPServer::HTTPServer(shared_ptr<ServerState> state)
HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
: AsyncHTTPServer(state->io_context, "[HTTPServer] "), state(state) {
using RouterRetT = std::variant<RawResponse, std::shared_ptr<const phosg::JSON>>;
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 {
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 {
auto command = args.post_data.get_string("command");
try {
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")}}));
} catch (const exception& e) {
co_return std::make_shared<phosg::JSON>(phosg::JSON::dict({{"result", phosg::join(dispatch_res, "\n")}}));
} catch (const std::exception& e) {
throw HTTPError(400, e.what());
}
});
@@ -70,8 +68,8 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
}
};
auto escape_label = +[](const string& in) -> string {
string out;
auto escape_label = +[](const std::string& in) -> std::string {
std::string out;
for (char ch : in) {
if (ch == '\\') {
out += "\\\\";
@@ -86,14 +84,14 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
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 += " ";
out += std::to_string(value);
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 += "{";
out += label_name;
@@ -104,9 +102,9 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
out += "\n";
};
map<string, uint64_t> connected_by_version;
map<string, uint64_t> lobby_players_by_version;
map<string, uint64_t> game_players_by_version;
std::map<std::string, uint64_t> connected_by_version;
std::map<std::string, uint64_t> lobby_players_by_version;
std::map<std::string, uint64_t> game_players_by_version;
uint64_t connected_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);
string revision = escape_label(GIT_REVISION_HASH);
std::string server_name = escape_label(this->state->name);
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 += "# TYPE pso_newserv_up gauge\n";
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 {
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()) {
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("Level", p->disp.stats.level.load() + 1);
client_json.emplace("NameColor", p->disp.visual.name_color.load());
client_json.emplace("ExtraModel", (p->disp.visual.validation_flags & 2) ? p->disp.visual.extra_model : phosg::JSON(nullptr));
client_json.emplace("SectionID", name_for_section_id(p->disp.visual.section_id));
client_json.emplace("CharClass", name_for_char_class(p->disp.visual.char_class));
client_json.emplace("Costume", p->disp.visual.costume.load());
client_json.emplace("Skin", p->disp.visual.skin.load());
client_json.emplace("Face", p->disp.visual.face.load());
client_json.emplace("Head", p->disp.visual.head.load());
client_json.emplace("Hair", p->disp.visual.hair.load());
client_json.emplace("HairR", p->disp.visual.hair_r.load());
client_json.emplace("HairG", p->disp.visual.hair_g.load());
client_json.emplace("HairB", p->disp.visual.hair_b.load());
client_json.emplace("ProportionX", p->disp.visual.proportion_x.load());
client_json.emplace("ProportionY", p->disp.visual.proportion_y.load());
client_json.emplace("NameColor", p->disp.visual.sh.name_color.load());
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.sh.section_id));
client_json.emplace("CharClass", name_for_char_class(p->disp.visual.sh.char_class));
client_json.emplace("Costume", p->disp.visual.sh.costume.load());
client_json.emplace("Skin", p->disp.visual.sh.skin.load());
client_json.emplace("Face", p->disp.visual.sh.face.load());
client_json.emplace("Head", p->disp.visual.sh.head.load());
client_json.emplace("Hair", p->disp.visual.sh.hair.load());
client_json.emplace("HairR", p->disp.visual.sh.hair_r.load());
client_json.emplace("HairG", p->disp.visual.sh.hair_g.load());
client_json.emplace("HairB", p->disp.visual.sh.hair_b.load());
client_json.emplace("ProportionX", p->disp.visual.sh.proportion_x.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("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;
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) {
for (EnemyType type : enemy_types_for_rare_table_index(
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 {
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) {
auto leader = l->clients[l->leader_id];
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();
}
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());
}
}
@@ -656,7 +654,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
});
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()) {
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 {
uint32_t account_id = args.get_param<uint32_t>("account_id");
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&) {
throw HTTPError(404, "Account does not exist");
}
});
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()) {
res->emplace(std::format("{}", it->team_id), it->json());
}
@@ -686,7 +684,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
if (!team) {
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 {
@@ -727,7 +725,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
};
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 {
@@ -742,12 +740,12 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
clients_json.emplace_back(phosg::JSON::dict({
{"ID", c->id},
{"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())},
{"Language", name_for_language(c->language())},
{"Level", p ? (p->disp.stats.level + 1) : phosg::JSON(nullptr)},
{"Class", p ? name_for_char_class(p->disp.visual.char_class) : phosg::JSON(nullptr)},
{"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : 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.sh.section_id) : phosg::JSON(nullptr)},
{"LobbyID", l ? l->lobby_id : phosg::JSON(nullptr)},
{"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)},
{"Games", std::move(games_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 {
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> {
return make_shared<phosg::JSON>(index->definitions_json());
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::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;
uint32_t card_id = args.get_param<uint32_t>("card_id");
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&) {
throw HTTPError(404, "Card definition does not exist");
}
});
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> {
auto ret = make_shared<phosg::JSON>(phosg::JSON::dict());
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::dict());
for (const auto& [map_number, map] : this->state->ep3_map_index->all_maps()) {
auto languages_json = phosg::JSON::list();
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 {
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 {
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")));
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&) {
throw HTTPError(404, "Map version does not exist");
}
@@ -851,7 +849,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
try {
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")));
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)};
} catch (const std::out_of_range&) {
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 {
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) {
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 {
try {
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> {
return make_shared<phosg::JSON>(table->json());
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::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");
}
});
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) {
ret->emplace_back(it.first);
}
@@ -890,7 +888,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
try {
const auto& table_name = args.params.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")) {
name_index = this->state->item_name_index_opt(Version::DC_V1);
} else if (table_name.ends_with("-v2")) {
@@ -900,17 +898,17 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
} else if (table_name.ends_with("-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> {
return make_shared<phosg::JSON>(table->json(name_index));
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
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");
}
});
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> {
return make_shared<phosg::JSON>(this->state->quest_index->json());
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
return std::make_shared<phosg::JSON>(this->state->quest_index->json());
});
});
@@ -920,21 +918,21 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
if (!q) {
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()) {
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
// 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();
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 {
co_await c->send_websocket_message(data);
} catch (const std::exception& e) {
@@ -954,14 +952,14 @@ asio::awaitable<void> HTTPServer::send_rare_drop_notification(shared_ptr<const p
co_return;
}
asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared_ptr<HTTPClient> c, HTTPRequest&& req) {
variant<RawResponse, shared_ptr<const phosg::JSON>> ret;
asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(std::shared_ptr<HTTPClient> c, HTTPRequest&& req) {
std::variant<RawResponse, std::shared_ptr<const phosg::JSON>> ret;
uint32_t serialize_options = phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY;
uint64_t start_time = phosg::now();
this->log.info_f("{} ...", req.path);
auto resp = make_unique<HTTPResponse>();
auto resp = std::make_unique<HTTPResponse>();
resp->http_version = req.http_version;
resp->response_code = 200;
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);
} 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;
} catch (const exception& e) {
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
} catch (const std::exception& e) {
ret = std::make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
resp->response_code = 500;
}
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
auto& json = get<shared_ptr<const phosg::JSON>>(ret);
auto& json = get<std::shared_ptr<const phosg::JSON>>(ret);
if (!json) {
co_return nullptr;
}
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);
});
uint64_t serialize_end = phosg::now();
string handler_time = phosg::format_duration(handler_end - start_time);
string serialize_time = phosg::format_duration(serialize_end - handler_end);
string size_str = phosg::format_size(resp->data.size());
this->log.info_f("{} in [handler: {}, serialize: {}, size: {}]", req.path, handler_time, serialize_time, size_str);
this->log.info_f("{} in [handler: {}, serialize: {}, size: {}]",
req.path,
phosg::format_duration(handler_end - start_time),
phosg::format_duration(serialize_end - handler_end),
phosg::format_size(resp->data.size()));
} else {
auto& raw_resp = get<RawResponse>(ret);
resp->headers.emplace("Content-Type", std::move(raw_resp.content_type));
resp->data = std::move(raw_resp.data);
string handler_time = phosg::format_duration(handler_end - start_time);
string size_str = phosg::format_size(resp->data.size());
this->log.info_f("{} in [handler: {}, size: {}]", req.path, handler_time, size_str);
this->log.info_f("{} in [handler: {}, size: {}]",
req.path,
phosg::format_duration(handler_end - start_time),
phosg::format_size(resp->data.size()));
}
co_return resp;
+14 -16
View File
@@ -4,8 +4,6 @@
#include <phosg/Strings.hh>
using namespace std;
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.
sum = (sum & 0xFFFF) + (sum >> 16);
@@ -65,13 +63,13 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
break;
default:
throw logic_error("invalid link type");
throw std::logic_error("invalid link type");
}
// Parse inner protocol headers
switch (proto) {
case Protocol::NONE:
throw runtime_error("unknown protocol");
throw std::runtime_error("unknown protocol");
case Protocol::LCP:
this->payload_size -= sizeof(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:
this->ipv4 = &r.get<IPv4Header>();
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);
@@ -95,7 +93,7 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
this->tcp = &r.get<TCPHeader>();
size_t tcp_header_size = (this->tcp->flags >> 12) * 4;
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->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);
}
string FrameInfo::header_str() const {
std::string FrameInfo::header_str() const {
if (!this->ether && !this->hdlc) {
return "<invalid-frame-info>";
}
string ret;
std::string ret;
if (this->ether) {
ret = std::format(
"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) {
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) {
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;
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 {
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);
}
@@ -252,10 +250,10 @@ uint16_t FrameInfo::computed_udp4_checksum(
uint16_t FrameInfo::computed_udp4_checksum() const {
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) {
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(
*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 {
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) {
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(
*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 {
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);
}
+132 -132
View File
@@ -13,18 +13,16 @@
#include "IPFrameInfo.hh"
#include "Loggers.hh"
using namespace std;
static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
uint8_t* data = reinterpret_cast<uint8_t*>(vdata);
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) {
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) {
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;
@@ -33,33 +31,34 @@ static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
uint8_t ch = data[read_offset++];
if (ch == 0x7D) {
if (read_offset >= size - 1) {
throw runtime_error("abort sequence received");
throw std::runtime_error("abort sequence received");
}
ch = data[read_offset++] ^ 0x20;
}
data[write_offset++] = ch;
}
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;
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) {
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);
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);
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()) {
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;
}
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);
}
@@ -114,7 +113,7 @@ void IPSSClient::TCPConnection::drain_outbound_data(size_t size) {
}
}
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(
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()),
sim(sim),
network_id(network_id),
@@ -143,7 +142,7 @@ IPSSClient::IPSSClient(
void IPSSClient::reschedule_idle_timeout() {
auto sim = this->sim.lock();
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.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
// data directly to the waiting coroutine's buffer, and put the rest in this->inbound_data if needed.
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);
data = reinterpret_cast<const uint8_t*>(data) + 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();
}
void IPSSChannel::send_raw(string&& data) {
void IPSSChannel::send_raw(std::string&& data) {
auto c = this->ipss_client.lock();
if (!c) {
return;
@@ -249,7 +248,7 @@ void IPSSChannel::send_raw(string&& data) {
asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
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
@@ -274,7 +273,7 @@ asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
this->recv_buf_size = size;
while (this->recv_buf) {
if (!this->connected()) {
throw runtime_error("IPSS channel closed");
throw std::runtime_error("IPSS channel closed");
}
this->data_available_signal.clear();
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) {
this->host_mac_address_bytes.clear(0x90);
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) {
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);
auto sock = make_shared<IPSSSocket>();
auto sock = std::make_shared<IPSSSocket>();
sock->name = name;
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
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) {
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);
}
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;
char addr_str[INET_ADDRSTRLEN];
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(
shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> conn) {
std::string IPStackSimulator::str_for_tcp_connection(
std::shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> 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);
string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn->client_port);
std::string server_netloc_str = str_for_ipv4_netloc(conn->server_addr, conn->server_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);
}
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 {
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;
switch (proto) {
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:
throw logic_error("cannot send LCP frame over Ethernet");
throw std::logic_error("cannot send LCP frame over Ethernet");
case FrameInfo::Protocol::IPV4:
header.ether.protocol = 0x0800;
break;
@@ -368,16 +367,17 @@ asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
header.ether.protocol = 0x0806;
break;
default:
throw logic_error("unknown layer 3 protocol");
throw std::logic_error("unknown layer 3 protocol");
}
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);
}
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;
hdlc.start_sentinel1 = 0x7E;
@@ -385,7 +385,7 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
hdlc.control = 0x03;
switch (proto) {
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:
hdlc.protocol = 0xC021;
break;
@@ -399,9 +399,9 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
hdlc.protocol = 0x0021;
break;
case FrameInfo::Protocol::ARP:
throw runtime_error("cannot send ARP packets over HDLC");
throw std::runtime_error("cannot send ARP packets over HDLC");
default:
throw logic_error("unknown layer 3 protocol");
throw std::logic_error("unknown layer 3 protocol");
}
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_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())) {
phosg::print_data(stderr, w.str());
}
if (!is_raw) {
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(escaped.data(), escaped.size())};
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(
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) {
case VirtualNetworkProtocol::ETHERNET_TAPSERVER:
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);
break;
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::ETHERNET
: 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)) {
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)) {
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) {
uint16_t expected_checksum = fi.computed_hdlc_checksum();
uint16_t stored_checksum = fi.stored_hdlc_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));
}
} else {
throw runtime_error("frame is not Ethernet or HDLC");
throw std::runtime_error("frame is not Ethernet or HDLC");
}
if (fi.lcp) {
@@ -489,18 +489,18 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
} else if (fi.ipv4) {
uint16_t expected_ipv4_checksum = fi.computed_ipv4_header_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));
}
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) {
uint16_t expected_udp_checksum = fi.computed_udp4_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));
}
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) {
uint16_t expected_tcp_checksum = fi.computed_tcp4_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));
}
co_await this->on_client_tcp_frame(c, fi);
} else {
throw runtime_error("frame uses unsupported IPv4 protocol");
throw std::runtime_error("frame uses unsupported IPv4 protocol");
}
} 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) {
case 0x01: { // Configure-Request
auto opts_r = fi.read_payload();
while (!opts_r.eof()) {
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);
switch (opt) {
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 0x07: // Protocol 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:
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
@@ -591,14 +591,14 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
case 0x05: { // Terminate-Request
c->ipv4_addr = 0;
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
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
break;
}
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
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
break;
@@ -614,23 +614,23 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
case 0x07: // Code-Reject
case 0x08: // Protocol-Reject
case 0x0A: // Echo-Reply
throw runtime_error("unimplemented LCP command");
throw std::runtime_error("unimplemented LCP command");
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
throw runtime_error("client sent incorrect PAP command");
throw std::runtime_error("client sent incorrect PAP command");
}
auto r = fi.read_payload();
string username = r.read(r.get_u8());
string password = r.read(r.get_u8());
std::string username = 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);
static const string login_message = "newserv PPP simulator";
static const std::string login_message = "newserv PPP simulator";
phosg::StringWriter w;
w.put<PAPHeader>(PAPHeader{
.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());
}
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) {
case 0x01: { // Configure-Request
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;
while (!opts_r.eof()) {
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);
switch (opt) {
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
rejected_opts_w.put_u8(0x02);
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 0x84: // Secondary NBNS server 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:
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
c->ipv4_addr = 0;
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
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
break;
@@ -768,21 +768,21 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
case 0x04: // Configure-Reject
case 0x06: // Terminate-Ack
case 0x07: // Code-Reject
throw runtime_error("unimplemented IPCP command");
throw std::runtime_error("unimplemented IPCP command");
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 ||
fi.arp->paddr_len != 4 ||
fi.arp->hardware_type != 0x0001 ||
fi.arp->protocol_type != 0x0800) {
throw runtime_error("unsupported ARP parameters");
throw std::runtime_error("unsupported ARP parameters");
}
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) {
@@ -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());
}
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.
// 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.checksum filled in later
string r_data;
std::string r_data;
if (fi.udp->dest_port == 67) { // DHCP
auto r = fi.read_payload();
const auto& dhcp = r.get<DHCPHeader>();
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) {
throw runtime_error("unknown DHCP hardware address length");
throw std::runtime_error("unknown DHCP hardware address length");
}
if (dhcp.magic != 0x63825363) {
throw runtime_error("incorrect DHCP magic cookie");
throw std::runtime_error("incorrect DHCP magic cookie");
}
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 (;;) {
uint8_t option = r.get_u8();
if (option == 0xFF) {
@@ -868,8 +868,8 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
uint8_t command = 0;
try {
command = option_data.at(53).at(0);
} catch (const out_of_range&) {
throw runtime_error("client did not send a DHCP command option");
} catch (const std::out_of_range&) {
throw std::runtime_error("client did not send a DHCP command option");
}
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;
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;
@@ -951,19 +951,19 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
r_data = std::move(w.str());
} 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
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);
r_data = DNSServer::response_for_query(fi.payload, fi.payload_size, resolved_address);
} 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()) {
@@ -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());
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);
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})",
fi.tcp->seq_num, fi.tcp->ack_num);
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) {
// 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.
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);
@@ -1015,19 +1015,19 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
break;
case 2: // Max segment size
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();
break;
case 3: // Window scale (ignored)
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);
break;
case 4: // Selective ACK supported (ignored)
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;
case 5: // Selective ACK (ignored)
@@ -1035,21 +1035,21 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
break;
case 8: // Timestamps (ignored)
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);
break;
default:
throw runtime_error("invalid TCP option");
throw std::runtime_error("invalid TCP option");
}
}
shared_ptr<IPSSClient::TCPConnection> conn;
string conn_str;
std::shared_ptr<IPSSClient::TCPConnection> conn;
std::string conn_str;
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
auto conn_it = c->tcp_connections.find(key);
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);
conn->server_addr = fi.ipv4->dest_addr;
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
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
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);
if (conn->awaiting_first_ack) {
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->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);
uint32_t ack_delta = fi.tcp->ack_num - conn->acked_server_seq;
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);
@@ -1125,7 +1125,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
ack_delta, 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)) {
bool is_rst = (fi.tcp->flags & TCPHeader::Flag::RST);
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);
if (conn->server_channel) {
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
// 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)
: "";
@@ -1190,7 +1190,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
}
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) {
@@ -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.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) {
return;
}
@@ -1262,7 +1262,7 @@ void IPStackSimulator::schedule_send_pending_push_frame(shared_ptr<IPSSClient::T
}
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->server_channel || !conn->server_channel->connected()) {
co_await this->close_tcp_connection(c, conn);
@@ -1270,11 +1270,11 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
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) {
// 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.
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",
@@ -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) {
// 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
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);
conn->awaiting_ack = true;
@@ -1300,17 +1300,17 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
if (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(
shared_ptr<IPSSClient> c,
shared_ptr<IPSSClient::TCPConnection> conn,
std::shared_ptr<IPSSClient> c,
std::shared_ptr<IPSSClient::TCPConnection> conn,
uint16_t flags,
const void* payload_data,
size_t payload_size) {
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;
@@ -1350,12 +1350,12 @@ asio::awaitable<void> IPStackSimulator::send_tcp_frame(
}
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) {
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
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;
conn->server_channel = make_shared<IPSSChannel>(
conn->server_channel = std::make_shared<IPSSChannel>(
this->shared_from_this(),
c,
conn,
@@ -1404,13 +1404,13 @@ asio::awaitable<void> IPStackSimulator::open_server_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
// an RST to us when disconnecting, so whatever
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::RST);
// 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);
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++;
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) {
for (;;) {
le_uint16_t frame_size;
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);
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
@@ -1443,7 +1443,7 @@ asio::awaitable<void> IPStackSimulator::handle_tapserver_client(std::shared_ptr<
try {
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())) {
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;
while (buffer.size() > frame_start_offset) {
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);
if (frame_end_offset == string::npos) {
if (frame_end_offset == std::string::npos) {
break;
}
frame_end_offset++;
@@ -1483,7 +1483,7 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
try {
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())) {
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
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) {
buffer_bytes = 0;
} else if (frame_start_offset > 0) {
+4 -6
View File
@@ -1,7 +1,5 @@
#include "IPV4RangeSet.hh"
using namespace std;
IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
for (const auto& it : json.as_list()) {
// 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) {
mask_bits = stoul(tokens[1], nullptr, 10);
if (mask_bits > 32) {
throw runtime_error("invalid IPv4 address range");
throw std::runtime_error("invalid IPv4 address range");
}
} else {
throw runtime_error("invalid IPv4 address range");
throw std::runtime_error("invalid IPv4 address range");
}
auto addr_tokens = phosg::split(tokens[0], '.');
if (addr_tokens.size() != 4) {
throw runtime_error("invalid IPv4 address");
throw std::runtime_error("invalid IPv4 address");
}
uint32_t addr = 0;
for (size_t z = 0; z < 4; z++) {
size_t end_pos = 0;
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
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;
}
+13 -11
View File
@@ -7,8 +7,6 @@
#include "Text.hh"
using namespace std;
struct GVMFileEntry {
be_uint16_t file_num;
pstring<TextEncoding::ASCII, 0x1C> name;
@@ -35,21 +33,25 @@ struct GVRHeader {
be_uint16_t height;
} __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;
{
size_t h = img.get_height();
size_t w = img.get_width();
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++) {
}
if (dimensions_field < 1) {
throw runtime_error("image is too small");
throw std::runtime_error("image is too small");
}
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;
break;
default:
throw invalid_argument("cannot encode pixel format");
throw std::invalid_argument("cannot encode pixel format");
}
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));
break;
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());
}
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 height = num_pixels / width;
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);
}
string encode_fon(const phosg::ImageRGB888& img) {
std::string encode_fon(const phosg::ImageRGB888& img) {
phosg::BitWriter w;
for (size_t y = 0; y < img.get_height(); y++) {
for (size_t x = 0; x < img.get_width(); x++) {
+57 -63
View File
@@ -19,22 +19,17 @@
#include "SaveFileFormats.hh"
#include "Text.hh"
using namespace std;
IntegralExpression::IntegralExpression(const string& text)
: root(this->parse_expr(text)) {}
IntegralExpression::IntegralExpression(const std::string& text) : root(this->parse_expr(text)) {}
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
: type(type),
left(std::move(left)),
right(std::move(right)) {}
Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right)
: type(type), left(std::move(left)), right(std::move(right)) {}
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
try {
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;
} catch (const bad_cast&) {
} catch (const std::bad_cast&) {
return false;
}
}
@@ -78,11 +73,11 @@ int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
case Type::MODULUS:
return this->left->evaluate(env) % this->right->evaluate(env);
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) {
case Type::LOGICAL_OR:
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
@@ -121,19 +116,18 @@ string IntegralExpression::BinaryOperatorNode::str() const {
case Type::MODULUS:
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
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)
: type(type),
sub(std::move(sub)) {}
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub)
: type(type), sub(std::move(sub)) {}
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
try {
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
return other_un.type == this->type && *other_un.sub == *this->sub;
} catch (const bad_cast&) {
} catch (const std::bad_cast&) {
return false;
}
}
@@ -147,11 +141,11 @@ int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
case Type::NEGATIVE:
return -this->sub->evaluate(env);
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) {
case Type::LOGICAL_NOT:
return "!(" + this->sub->str() + ")";
@@ -160,7 +154,7 @@ string IntegralExpression::UnaryOperatorNode::str() const {
case Type::NEGATIVE:
return "-(" + this->sub->str() + ")";
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 {
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
return other_flag.flag_index == this->flag_index;
} catch (const bad_cast&) {
} catch (const std::bad_cast&) {
return false;
}
}
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
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;
}
string IntegralExpression::FlagLookupNode::str() const {
std::string IntegralExpression::FlagLookupNode::str() const {
return std::format("F_{:04X}", this->flag_index);
}
@@ -193,14 +187,14 @@ bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& o
try {
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
} catch (const bad_cast&) {
} catch (const std::bad_cast&) {
return false;
}
}
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
if (!env.challenge_records) {
throw runtime_error("challenge records not available");
throw std::runtime_error("challenge records not available");
}
if (this->episode == Episode::EP1) {
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;
}
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));
}
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 {
try {
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
return other_team_reward.reward_name == this->reward_name;
} catch (const bad_cast&) {
} catch (const std::bad_cast&) {
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;
}
string IntegralExpression::TeamRewardLookupNode::str() const {
std::string IntegralExpression::TeamRewardLookupNode::str() const {
return "T_" + this->reward_name;
}
@@ -243,7 +237,7 @@ int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const
return env.num_players;
}
string IntegralExpression::NumPlayersLookupNode::str() const {
std::string IntegralExpression::NumPlayersLookupNode::str() const {
return "V_NumPlayers";
}
@@ -257,7 +251,7 @@ int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
return env.event;
}
string IntegralExpression::EventLookupNode::str() const {
std::string IntegralExpression::EventLookupNode::str() const {
return "V_Event";
}
@@ -271,7 +265,7 @@ int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const
return env.v1_present ? 1 : 0;
}
string IntegralExpression::V1PresenceLookupNode::str() const {
std::string IntegralExpression::V1PresenceLookupNode::str() const {
return "V_V1Present";
}
@@ -282,7 +276,7 @@ bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
try {
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
return other_const.value == this->value;
} catch (const bad_cast&) {
} catch (const std::bad_cast&) {
return false;
}
}
@@ -291,11 +285,11 @@ int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
return this->value;
}
string IntegralExpression::ConstantNode::str() const {
std::string IntegralExpression::ConstantNode::str() const {
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
for (;;) {
size_t starting_size = text.size();
@@ -329,22 +323,22 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
}
}
if (text.empty()) {
throw runtime_error("invalid expression");
throw std::runtime_error("invalid expression");
}
// Check for binary operators at the root level
using BinType = BinaryOperatorNode::Type;
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
{{make_pair("||", BinType::LOGICAL_OR)}},
{{make_pair("&&", BinType::LOGICAL_AND)}},
{{make_pair("|", BinType::BITWISE_OR)}},
{{make_pair("^", BinType::BITWISE_XOR)}},
{{make_pair("&", BinType::BITWISE_AND)}},
{{make_pair("==", BinType::EQUAL)}, {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)}},
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
static const std::vector<std::vector<std::pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
{{std::make_pair("||", BinType::LOGICAL_OR)}},
{{std::make_pair("&&", BinType::LOGICAL_AND)}},
{{std::make_pair("|", BinType::BITWISE_OR)}},
{{std::make_pair("^", BinType::BITWISE_XOR)}},
{{std::make_pair("&", BinType::BITWISE_AND)}},
{{std::make_pair("==", BinType::EQUAL)}, {std::make_pair("!=", BinType::NOT_EQUAL)}},
{{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)}},
{{std::make_pair("<<", BinType::LEFT_SHIFT)}, {std::make_pair(">>", BinType::RIGHT_SHIFT)}},
{{std::make_pair("+", BinType::ADD)}, {std::make_pair("-", BinType::SUBTRACT)}},
{{std::make_pair("*", BinType::MULTIPLY)}, {std::make_pair("/", BinType::DIVIDE)}, {std::make_pair("%", BinType::MODULUS)}},
};
for (const auto& operators : binary_operator_levels) {
size_t paren_level = 0;
@@ -390,12 +384,12 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
char* endptr = nullptr;
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
if (endptr != text.data() + text.size()) {
throw runtime_error("invalid flag lookup token");
throw std::runtime_error("invalid flag lookup token");
}
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_")) {
Episode episode;
@@ -404,45 +398,45 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
} else if (text.starts_with("CC_Ep2_")) {
episode = Episode::EP2;
} else {
throw runtime_error("invalid challenge episode");
throw std::runtime_error("invalid challenge episode");
}
char* endptr = nullptr;
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
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)) {
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_")) {
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
return std::make_unique<TeamRewardLookupNode>(std::string(text.substr(2)));
}
if (text == "V_NumPlayers") {
return make_unique<NumPlayersLookupNode>();
return std::make_unique<NumPlayersLookupNode>();
}
if (text == "V_Event") {
return make_unique<EventLookupNode>();
return std::make_unique<EventLookupNode>();
}
if (text == "V_V1Present") {
return make_unique<V1PresenceLookupNode>();
return std::make_unique<V1PresenceLookupNode>();
}
// Check for constants
if (text == "true") {
return make_unique<ConstantNode>(1);
return std::make_unique<ConstantNode>(1);
}
if (text == "false") {
return make_unique<ConstantNode>(0);
return std::make_unique<ConstantNode>(0);
}
try {
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()) {
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
View File
@@ -6,8 +6,6 @@
#include "EnemyType.hh"
#include "Loggers.hh"
using namespace std;
// The favored weapon type table is hardcoded in the game client. The table is:
// Viridia shots
// Greennill rifles
@@ -19,23 +17,23 @@ using namespace std;
// Oran daggers
// Yellowboze (none)
// 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};
ItemCreator::ItemCreator(
shared_ptr<const CommonItemSet> common_item_set,
shared_ptr<const RareItemSet> rare_item_set,
shared_ptr<const ArmorRandomSet> armor_random_set,
shared_ptr<const ToolRandomSet> tool_random_set,
shared_ptr<const WeaponRandomSet> weapon_random_set,
shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const CommonItemSet> common_item_set,
std::shared_ptr<const RareItemSet> rare_item_set,
std::shared_ptr<const ArmorRandomSet> armor_random_set,
std::shared_ptr<const ToolRandomSet> tool_random_set,
std::shared_ptr<const WeaponRandomSet> weapon_random_set,
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const ItemData::StackLimits> stack_limits,
GameMode mode,
Difficulty difficulty,
uint8_t section_id,
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),
logic_version(stack_limits->version),
is_legacy_replay(false),
@@ -175,7 +173,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
case 6: // Nothing
break;
default:
throw logic_error("this should be impossible");
throw std::logic_error("this should be impossible");
}
if (item_class < 6) {
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;
} catch (const exception& e) {
} catch (const std::exception& e) {
this->log.error_f("Exception in item creation: {}", e.what());
return DropResult();
}
@@ -233,13 +231,13 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
case 2:
try {
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");
item_class = 0xFF;
}
break;
default:
throw logic_error("invalid item class determinant");
throw std::logic_error("invalid item class determinant");
}
this->log.info_f(
@@ -265,7 +263,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
res.item.data1[0] = 0x04;
try {
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");
return DropResult();
}
@@ -281,7 +279,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
return res;
} catch (const exception& e) {
} catch (const std::exception& e) {
this->log.error_f("Exception in item creation: {}", e.what());
return DropResult();
}
@@ -408,7 +406,7 @@ ItemData ItemCreator::create_rare_item(const ItemData& drop_item, uint8_t area)
case 4:
break;
default:
throw logic_error("invalid item class");
throw std::logic_error("invalid item class");
}
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);
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) {
@@ -557,7 +555,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
item.clear();
break;
default:
throw logic_error("invalid weapon and armor mode");
throw std::logic_error("invalid weapon and armor mode");
}
break;
case 2:
@@ -588,7 +586,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
}
break;
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) {
this->log.info_f("Restricted: scape dolls not allowed");
@@ -602,7 +600,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
}
break;
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:
// item.clear();
// item.data1[0] = 0x05;
throw logic_error("invalid item class");
throw std::logic_error("invalid item class");
}
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[1] = data.first;
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");
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) {
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);
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::GC_EP3_NTE:
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:
star_base_index = 0x124;
num_units = 0x43;
@@ -890,7 +888,7 @@ void ItemCreator::generate_unit_stars_tables() {
num_units = 0x64;
break;
default:
throw logic_error("invalid game version");
throw std::logic_error("invalid game version");
}
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];
}
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);
@@ -955,7 +953,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
}
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>
@@ -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);
}
vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
vector<ItemData> shop;
std::vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
std::vector<ItemData> shop;
this->generate_armor_shop_armors(shop, episode, player_level);
this->generate_armor_shop_shields(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(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
(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(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
(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(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
size_t similar_items = 0;
for (const auto& shop_item : shop) {
// 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(
const vector<ItemData>& shop, const ItemData& item) {
const std::vector<ItemData>& shop, const ItemData& item) {
for (const auto& shop_item : shop) {
if ((shop_item.data1[0] == item.data1[0]) &&
(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;
}
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;
if (player_level < 11) {
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;
if (player_level < 11) {
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;
if (player_level < 11) {
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) {
vector<ItemData> shop;
std::vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
std::vector<ItemData> shop;
this->generate_common_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);
@@ -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},
{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;
if (player_level < 11) {
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) {
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;
if (player_level < 11) {
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);
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,
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);
auto table = this->tool_random_set->get_tech_disk_level_table(table_index);
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];
@@ -1318,23 +1316,23 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
item.data1[2] = 0;
break;
case ToolRandomSet::TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR:
item.data1[2] = clamp<ssize_t>(
(min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
item.data1[2] = std::clamp<ssize_t>(
(std::min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
break;
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
// 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.
int16_t min_level = 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);
int16_t min_level = std::max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
item.data1[2] = std::clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
break;
}
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;
if (player_level < 11) {
num_items = 10;
@@ -1385,13 +1383,13 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
}
pt.shuffle(this->rand_crypt);
vector<ItemData> shop;
std::vector<ItemData> shop;
while (shop.size() < num_items) {
ItemData item;
uint8_t which = pt.pop();
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},
{0x59, 0x00}, {0x8A, 0x00}, {0x99, 0x00}};
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;
} 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},
{0x2A, 0x00}, {0x48, 0x00}, {0x35, 0x00}};
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;
} else {
static const vector<pair<uint8_t, uint8_t>> defs({
static const std::vector<std::pair<uint8_t, uint8_t>> defs({
/* 00 */ {0x01, 0x00},
/* 01 */ {0x01, 0x01},
/* 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);
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) {
@@ -1574,11 +1572,11 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe
item.data1[4] = this->choose_weapon_special(1);
break;
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};
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;
} else {
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;
} else {
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;
break;
default:
throw runtime_error("invalid item class");
throw std::runtime_error("invalid item class");
}
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) {
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];
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");
}
}
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
// Invalid special number passed to get_special; just ignore it
}
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);
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);
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);
this->log.info_f("(Grind) Luck is now {}", luck);
} 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.
for (size_t z = 6; z <= 10; z += 2) {
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);
+30 -32
View File
@@ -6,11 +6,9 @@
#include "ItemParameterTable.hh"
#include "StaticGameData.hh"
using namespace std;
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE({10});
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(
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 std::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});
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);
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),
max_tool_stack_sizes_by_data1_1(max_tool_stack_sizes_by_data1_1),
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) {
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;
}
@@ -181,7 +179,7 @@ bool ItemData::is_wrapped(const StackLimits& limits) const {
case 4:
return false;
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:
break;
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:
break;
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;
}
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) {
ret |= 0x100;
}
@@ -343,10 +341,10 @@ uint8_t ItemData::mag_photon_blast_for_slot(uint8_t slot) const {
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 {
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--;
}
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);
flags |= 4;
@@ -471,11 +469,11 @@ void ItemData::decode_for_version(Version from_version) {
break;
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 &&
(is_v1(to_version) || is_v2(to_version)) &&
(to_version != Version::GC_NTE) &&
@@ -501,7 +499,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
break;
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]])) {
this->data1[3] = this->data1[2];
this->data1[2] = 0x00;
@@ -554,7 +552,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
break;
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:
return (this->get_unit_bonus() > 0);
default:
throw runtime_error("invalid item");
throw std::runtime_error("invalid item");
}
case 2:
if (this->data1[1] < 0x23) {
@@ -670,7 +668,7 @@ bool ItemData::has_bonuses() const {
case 4:
return false;
default:
throw runtime_error("invalid item");
throw std::runtime_error("invalid item");
}
}
@@ -703,7 +701,7 @@ EquipSlot ItemData::default_equip_slot() const {
case 0x02:
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 {
@@ -722,7 +720,7 @@ bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
case EquipSlot::WEAPON:
return (this->data1[0] == 0x00);
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;
}
ItemData ItemData::from_data(const string& data) {
ItemData ItemData::from_data(const std::string& data) {
if (data.size() < 2) {
throw runtime_error("data is too short");
throw std::runtime_error("data is too short");
}
if (data.size() > 0x10) {
throw runtime_error("data is too long");
throw std::runtime_error("data is too long");
}
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];
}
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];
}
if (ret.data1[0] > 4) {
throw runtime_error("invalid item class");
throw std::runtime_error("invalid item class");
}
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 ret;
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[1] = (primary_identifier >> 16) & 0xFF;
@@ -786,16 +784,16 @@ ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t p
return ret;
}
string ItemData::hex() const {
std::string ItemData::hex() const {
return std::format("{:08X} {:08X} {:08X} ({:08X}) {:08X}",
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}",
this->data1db[0], this->data1db[1], this->data1db[2], this->data2db);
size_t offset = ret.find_last_not_of('0');
if (offset != string::npos) {
if (offset != std::string::npos) {
offset += (offset & 1) ? 1 : 2;
offset = std::max<size_t>(offset, 6);
if (offset < ret.size()) {
+59 -62
View File
@@ -4,8 +4,6 @@
#include "StaticGameData.hh"
using namespace std;
ItemNameIndex::ItemNameIndex(
std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const ItemData::StackLimits> limits,
@@ -19,17 +17,17 @@ ItemNameIndex::ItemNameIndex(
continue;
}
const string* name = nullptr;
const std::string* name = nullptr;
bool is_es_weapon = false;
try {
ItemData item = ItemData::from_primary_identifier(*this->limits, primary_identifier);
is_es_weapon = item.is_s_rank_weapon();
name = &name_coll.at(item_parameter_table->get_item_id(item));
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (name) {
auto meta = make_shared<ItemMetadata>();
auto meta = std::make_shared<ItemMetadata>();
meta->primary_identifier = primary_identifier;
meta->name = *name;
this->primary_identifier_index.emplace(meta->primary_identifier, meta);
@@ -44,7 +42,7 @@ ItemNameIndex::ItemNameIndex(
static std::string s_rank_name_characters("\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", 0x20);
// 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,
"Draw", // Type: 0001, amount: 0005
"Drain", // Type: 0001, amount: 0009
@@ -89,7 +87,7 @@ static const array<const char*, 0x29> name_for_weapon_special = {
};
// 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,
"Jellen",
"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);
}
vector<string> ret_tokens;
std::vector<std::string> ret_tokens;
// For weapons, specials appear before the weapon name
bool is_unidentified = false;
@@ -135,7 +133,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
if (special_id) {
try {
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));
}
}
@@ -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()) {
try {
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]));
}
}
@@ -160,11 +158,11 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
// Add the item name
uint32_t primary_identifier = item.primary_identifier();
if ((primary_identifier & 0xFFFF0000) == 0x03020000) {
string technique_name;
std::string technique_name;
try {
technique_name = tech_id_to_name.at(item.data1[4]);
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]);
}
// 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 {
auto meta = this->primary_identifier_index.at(primary_identifier);
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));
}
}
@@ -208,7 +206,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
static_cast<uint8_t>(be_data1w5 & 0x1F),
};
string name;
std::string name;
for (size_t x = 0; x < 8; x++) {
char ch = s_rank_name_characters.at(char_indexes[x]);
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 dex = item.data1w[4];
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;
uint8_t partial = stat % 100;
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];
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};
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];
}
string token = "PB:";
std::string token = "PB:";
for (size_t x = 0; x < 3; x++) {
if (pb_names[x] == nullptr) {
continue;
@@ -354,7 +352,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
try {
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]));
}
@@ -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 (is_unidentified) {
return "$C3" + ret;
@@ -387,18 +385,18 @@ ItemData ItemNameIndex::parse_item_description(const std::string& desc) const {
ItemData ret;
try {
ret = this->parse_item_description_phase(desc, false);
} catch (const exception& e1) {
} catch (const std::exception& e1) {
try {
ret = this->parse_item_description_phase(desc, true);
} catch (const exception& e2) {
} catch (const std::exception& e2) {
try {
ret = ItemData::from_data(phosg::parse_data_string(desc));
} catch (const exception& ed) {
} catch (const std::exception& ed) {
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()));
} 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()));
}
}
@@ -414,17 +412,17 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
ret.id = 0xFFFFFFFF;
ret.data2d = 0;
string desc = phosg::tolower(description);
std::string desc = phosg::tolower(description);
if (desc.ends_with(" meseta")) {
ret.data1[0] = 0x04;
ret.data2d = stol(desc, nullptr, 10);
ret.data2d = std::stol(desc, nullptr, 10);
return ret;
}
if (desc.starts_with("es ")) {
auto parse_name = [&](const std::string& token) -> void {
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};
@@ -432,7 +430,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
char ch = toupper(token[z]);
size_t pos = s_rank_name_characters.find(ch);
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;
}
@@ -538,10 +536,10 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
ret.data1[4] = tech;
} else {
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.")) {
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 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]) {
continue;
}
string prefix = phosg::tolower(name_for_weapon_special[z]);
std::string prefix = phosg::tolower(name_for_weapon_special[z]);
prefix += ' ';
if (desc.starts_with(prefix)) {
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)) {
break;
} else if (name_it == this->name_index.begin()) {
throw runtime_error("no such item");
throw std::runtime_error("no such item");
} else {
name_it--;
lookback++;
}
}
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());
@@ -637,7 +635,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
} else {
auto p_tokens = phosg::split(token, '/');
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 bonus_index = 0;
@@ -647,7 +645,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
continue;
}
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[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) {
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},
{"-", 0xFFFE},
{"", 0x0000},
@@ -698,7 +696,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
if (token.empty()) {
continue;
} 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")) {
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
auto pb_tokens = phosg::split(token.substr(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}});
for (const auto& pb_token : pb_tokens) {
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
auto s_tokens = phosg::split(token, '/');
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++) {
auto n_tokens = phosg::split(s_tokens[z], '.');
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)) {
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100;
} 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) {
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100 + stoul(n_tokens[1], nullptr, 10);
} else {
throw runtime_error("incorrect stat format");
throw std::runtime_error("incorrect stat format");
}
}
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;
}
} else if (!desc.empty()) {
throw runtime_error("item cannot be stacked");
throw std::runtime_error("item cannot be stacked");
}
if (is_wrapped) {
if (ret.is_stackable(*this->limits)) {
throw runtime_error("stackable items cannot be wrapped");
throw std::runtime_error("stackable items cannot be wrapped");
} else {
ret.data1[3] |= 0x40;
}
}
} else {
throw logic_error("invalid item class");
throw std::logic_error("invalid item class");
}
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++) {
uint8_t weapon_class = pmt->get_weapon_kind(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, ' ');
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[1] = data1_1;
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);
string tech_boost_str;
std::string tech_boost_str;
if (w.tech_boost_entry_index < pmt->num_tech_boosts()) {
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})",
@@ -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");
for (size_t data1_1 = 1; data1_1 < 3; 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, ' ');
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[1] = data1_1;
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);
string tech_boost_str;
std::string tech_boost_str;
if (a.tech_boost_entry_index < pmt->num_tech_boosts()) {
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})",
@@ -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");
{
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, ' ');
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[1] = 0x03;
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",
data1_2,
@@ -975,14 +973,14 @@ void ItemNameIndex::print_table(FILE* stream) const {
const auto& m = pmt->get_mag(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, ' ');
ItemData item;
item.data1[0] = 0x02;
item.data1[1] = data1_1;
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",
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");
for (size_t data1_1 = 0; data1_1 < pmt->num_tool_classes(); 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, ' ');
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[(data1_1 == 0x02) ? 4 : 2] = data1_2;
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",
data1_1,
@@ -1078,7 +1076,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
if (index) {
try {
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);
@@ -1191,7 +1189,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
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-"};
string s;
std::string s;
if (sb.stat1 > 0x10) {
s = std::format("[{:02X}:{:04X}]", sb.stat1, sb.amount1);
} else if (sb.stat1 > 0) {
@@ -1244,7 +1242,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
phosg::fwrite_fmt(stream, " ## => BOOSTS\n");
for (size_t z = 0; z < pmt->num_tech_boosts(); z++) {
const auto& tb = pmt->get_tech_boost(z);
string s;
std::string s;
if (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[1] = item_code >> 8;
item.data1[2] = item_code;
string name = this->describe_item(item);
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, name);
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, this->describe_item(item));
}
phosg::fwrite_fmt(stream, "RANGED SPECIALS\n");
+42 -44
View File
@@ -2,8 +2,6 @@
#include "CommonFileFormats.hh"
using namespace std;
/* General notes on the ItemPMT formats:
*
* 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")) {
return ServerDropMode::SERVER_DUPLICATE;
} 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:
return "SERVER_DUPLICATE";
default:
throw runtime_error("invalid drop mode");
throw std::runtime_error("invalid drop mode");
}
}
@@ -1027,11 +1025,11 @@ public:
case 3:
return this->unit_sale_divisor;
}
throw runtime_error("invalid defensive item type");
throw std::runtime_error("invalid defensive item type");
case 2:
return this->mag_sale_divisor;
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
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 {
ItemData item(data1, 0);
@@ -2096,7 +2094,7 @@ std::set<uint32_t> ItemParameterTable::compute_all_valid_primary_identifiers() c
item.data1[position] = x;
try {
this->get_item_id(item);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
return x;
}
ret.emplace(item.primary_identifier());
@@ -2143,7 +2141,7 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie
case 3:
return &this->get_unit(data1_2);
default:
throw runtime_error("invalid primary identifier");
throw std::runtime_error("invalid primary identifier");
}
case 2:
return &this->get_mag(data1_1);
@@ -2152,7 +2150,7 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie
// 0302XXYY here
return &this->get_tool(data1_1, data1_2);
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)) {
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:
return this->get_mag(item.data1[1]).id;
case 3:
@@ -2175,11 +2173,11 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
} else {
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:
throw runtime_error("item is meseta and therefore has no definition");
throw std::runtime_error("item is meseta and therefore has no definition");
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)) {
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:
return this->get_mag(item.data1[1]).team_points;
case 3:
@@ -2202,11 +2200,11 @@ uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const {
} else {
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:
throw runtime_error("item is meseta and therefore has no definition");
throw std::runtime_error("item is meseta and therefore has no definition");
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 {
@@ -2288,7 +2286,7 @@ std::string ItemParameterTable::get_shield_stat_boost_index_table() const {
bool ItemParameterTable::is_item_rare(const ItemData& item) const {
try {
return (this->get_item_base_stars(item) >= 9);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
return false;
}
}
@@ -2317,8 +2315,8 @@ const std::vector<ItemParameterTable::ItemCombination>& ItemParameterTable::all_
try {
return this->item_combinations_index().at(item_code_to_u32(
used_item.data1[0], used_item.data1[1], used_item.data1[2]));
} catch (const out_of_range&) {
static const vector<ItemCombination> ret;
} catch (const std::out_of_range&) {
static const std::vector<ItemCombination> ret;
return ret;
}
}
@@ -2332,7 +2330,7 @@ const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combinat
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 {
@@ -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]);
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]);
@@ -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]);
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();
@@ -2406,9 +2404,9 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
return item.data2d;
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 <
@@ -2442,7 +2440,7 @@ public:
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);
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);
}
@@ -2470,14 +2468,14 @@ public:
virtual size_t num_weapons_in_class(uint8_t data1_1) const {
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);
}
virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const {
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;
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 {
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;
}
virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const {
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;
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 {
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);
}
virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const {
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;
auto it = this->tools.find(key);
@@ -2543,11 +2541,11 @@ public:
const auto* defs = &this->r.pget<ToolT>(co.offset, sizeof(ToolT) * co.count);
for (size_t y = 0; y < co.count; y++) {
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 {
@@ -2636,10 +2634,10 @@ public:
virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const {
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) {
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);
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 {
special &= 0x3F;
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) {
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 {
if (char_class >= 12) {
throw out_of_range("invalid character class");
throw std::out_of_range("invalid character class");
}
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; }) {
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; }) {
const auto& co = this->r.pget<ArrayRefT<BE>>(this->root->unwrap_table);
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* 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 {
return make_pair(nullptr, 0);
return std::make_pair(nullptr, 0);
}
}
+10 -12
View File
@@ -1,7 +1,5 @@
#include "ItemTranslationTable.hh"
using namespace std;
static constexpr bool is_canonical(uint32_t id) {
return !(id & 0x80000000);
}
@@ -23,12 +21,12 @@ ItemTranslationTable::ItemTranslationTable(
if (is_canonical(id)) {
has_any_canonical_id = true;
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) {
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];
if (is_canonical(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 {
item_parameter_table->definition_for_primary_identifier(e_id);
} catch (const 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));
} catch (const std::out_of_range&) {
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)) {
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))) {
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()) {
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) {
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) {
const auto& l = json.as_list();
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++) {
this->id_for_version[z] = l[z]->as_int();
+33 -35
View File
@@ -4,9 +4,7 @@
#include "SendCommands.hh"
using namespace std;
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomGenerator> rand_crypt) {
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<RandomGenerator> rand_crypt) {
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,
@@ -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
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) {
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]);
} else if ((primary_identifier & 0xFFFF0000) == 0x030A0000) { // Grinder
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)];
@@ -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 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)) {
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
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)) {
p->disp.stats.char_stats.lck += 2;
} else {
throw runtime_error("unknown material used");
throw std::runtime_error("unknown material used");
}
break;
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)) {
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
auto& armor = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::ARMOR)];
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]++;
@@ -137,10 +135,10 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
if (evolution_number < 4) {
switch (item.data.data1[2]) {
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;
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;
case 0x02: // Parts of RoboChao
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;
break;
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;
}
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
// 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 {
auto item_parameter_table = s->item_parameter_table(c->version());
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) {
throw runtime_error("item combination requires specific char_class");
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.sh.char_class) {
throw std::runtime_error("item combination requires specific char_class");
}
if (combo.mag_level != 0xFF) {
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) {
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 (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) {
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) {
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 (combo_applied) {
throw runtime_error("multiple combinations apply");
throw std::runtime_error("multiple combinations apply");
}
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.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(
ItemData& mag_item,
const ItemData& fed_item,
shared_ptr<const ItemParameterTable> item_parameter_table,
shared_ptr<const MagEvolutionTable> mag_evolution_table,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
uint8_t char_class,
uint8_t section_id,
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
{0x03000100, 1}, // Dimate
{0x03000200, 2}, // Trimate
@@ -298,7 +296,7 @@ void apply_mag_feed_result(
if ((delta > 0) || ((delta < 0) && (-delta < existing_stat))) {
uint16_t level = data.compute_mag_level();
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)) {
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, 4, feed_result.dex);
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[1] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[1]) + feed_result.iq, 0, 200);
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] = 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();
mag_item.data1[2] = mag_level;
@@ -347,7 +345,7 @@ void apply_mag_feed_result(
mag_item.data1[1] = 0x19; // Vritra
break;
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) {
table_index += 6;
} 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
@@ -433,7 +431,7 @@ void apply_mag_feed_result(
bool is_ranger = char_class_is_ranger(char_class);
bool is_force = char_class_is_force(char_class);
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) {
@@ -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,
s->item_parameter_table(c->version()),
s->mag_evolution_table(c->version()),
player->disp.visual.char_class,
player->disp.visual.section_id,
player->disp.visual.sh.char_class,
player->disp.visual.sh.section_id,
!is_v1_or_v2(c->version()));
}
+7 -9
View File
@@ -7,8 +7,6 @@
#include "CommonFileFormats.hh"
#include "StaticGameData.hh"
using namespace std;
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
stats.level = 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);
}
LevelTableV2::LevelTableV2(const string& data) {
LevelTableV2::LevelTableV2(const std::string& data) {
struct Root {
// 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.
@@ -165,7 +163,7 @@ size_t LevelTableV3::num_char_classes() 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
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 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);
}
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
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},
@@ -208,7 +206,7 @@ const LevelStatsDelta& LevelTableV3::stats_delta_for_level(uint8_t char_class, u
}
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:
// root:
// 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);
}
LevelTableXB::LevelTableXB(const string& data) {
LevelTableXB::LevelTableXB(const std::string& 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);
}
LevelTableV4::LevelTableV4(const string& data) {
LevelTableV4::LevelTableV4(const std::string& data) {
phosg::StringReader r(data);
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
+53 -55
View File
@@ -10,8 +10,6 @@
#include "ServerState.hh"
#include "Text.hh"
using namespace std;
bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
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);
}
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);
}
void Lobby::FloorItemManager::add(
const ItemData& item,
const VectorXZF& pos,
shared_ptr<const MapState::ObjectState> from_obj,
shared_ptr<const MapState::EnemyState> from_ene,
std::shared_ptr<const MapState::ObjectState> from_obj,
std::shared_ptr<const MapState::EnemyState> from_ene,
uint16_t flags) {
auto fi = make_shared<FloorItem>();
auto fi = std::make_shared<FloorItem>();
fi->data = item;
fi->pos = pos;
fi->drop_number = this->next_drop_number++;
@@ -44,14 +42,14 @@ void Lobby::FloorItemManager::add(
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) {
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);
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++) {
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) {
auto item_it = this->items.find(item_id);
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;
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++) {
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);
@@ -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() {
unordered_set<shared_ptr<FloorItem>> ret;
std::unordered_set<std::shared_ptr<FloorItem>> ret;
for (size_t z = 0; z < 12; z++) {
while (this->queue_for_client[z].size() > 48) {
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) {
unordered_set<uint32_t> item_ids_to_delete;
std::unordered_set<uint32_t> item_ids_to_delete;
for (const auto& it : this->items) {
if ((it.second->flags & remaining_clients_mask) == 0) {
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() {
unordered_set<uint32_t> item_ids_to_delete;
std::unordered_set<uint32_t> item_ids_to_delete;
for (const auto& it : this->items) {
if ((it.second->flags & 0x00F) != 0x00F) {
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) {
::map<uint32_t, shared_ptr<FloorItem>> old_items;
std::map<uint32_t, std::shared_ptr<FloorItem>> old_items;
old_items.swap(this->items);
for (auto& queue : this->queue_for_client) {
queue.clear();
@@ -142,13 +140,13 @@ uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t 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),
log(std::format("[{}:{:X}] ", is_game ? "Game" : "Lobby", id), lobby_log.min_level),
creation_time(phosg::now()),
lobby_id(id),
random_seed(phosg::random_object<uint32_t>()),
rand_crypt(make_shared<DisabledRandomGenerator>()),
rand_crypt(std::make_shared<DisabledRandomGenerator>()),
drop_mode(ServerDropMode::CLIENT),
idle_timeout_timer(*s->io_context) {
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);
}
shared_ptr<ServerState> Lobby::require_server_state() const {
std::shared_ptr<ServerState> Lobby::require_server_state() const {
auto s = this->server_state.lock();
if (!s) {
throw logic_error("server is deleted");
throw std::logic_error("server is deleted");
}
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) {
throw runtime_error("challenge params are missing");
throw std::runtime_error("challenge params are missing");
}
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;
}
shared_ptr<RandomGenerator> rand_crypt;
std::shared_ptr<RandomGenerator> 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 {
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();
if (effective_section_id >= 10) {
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->rare_item_set(logic_version, this->quest),
s->armor_random_set,
@@ -249,7 +247,7 @@ uint8_t Lobby::effective_section_id() const {
}
auto leader = this->clients.at(this->leader_id);
if (leader) {
return leader->character_file()->disp.visual.section_id;
return leader->character_file()->disp.visual.sh.section_id;
}
return 0xFF;
}
@@ -279,13 +277,13 @@ void Lobby::load_maps() {
if (this->quest) {
this->log.info_f("Loading quest supermap");
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);
} else {
this->log.info_f("Loading free play supermaps");
auto s = this->require_server_state();
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);
}
@@ -328,7 +326,7 @@ void Lobby::create_ep3_server() {
} else {
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();
}
@@ -386,9 +384,9 @@ bool Lobby::any_v1_clients_present() const {
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) {
throw runtime_error("client is not logged in");
throw std::runtime_error("client is not logged in");
}
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 (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;
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) {
throw out_of_range("no space left in lobby");
throw std::out_of_range("no space left in lobby");
}
} else {
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) {
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;
lobby_data.player_tag = 0x00010000;
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(
lobby_data,
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);
}
@@ -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) {
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 ({})",
c->lobby_client_id,
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) {
return;
}
if (required_client_id >= 0) {
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 {
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;
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);
}
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++) {
auto lc = this->clients[x];
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)) {
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;
}
}
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 {
@@ -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);
}
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);
}
@@ -683,7 +681,7 @@ void Lobby::add_item(
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);
m.add(fi);
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);
}
@@ -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();
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++) {
@@ -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 {
unordered_map<uint32_t, shared_ptr<Client>> ret;
std::unordered_map<uint32_t, std::shared_ptr<Client>> Lobby::clients_by_account_id() const {
std::unordered_map<uint32_t, std::shared_ptr<Client>> ret;
for (auto c : this->clients) {
if (c && c->login) {
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 {
size_t num_players = this->count_clients();
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;
for (const auto& lc : this->clients) {
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:
// 1. Priority class: has free space < empty (persistent) < full < non-joinable (in quest/battle)
// 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
// 5. Difficulty: Normal < Hard < Very Hard < Ultimate
// 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) ||
l->check_flag(Lobby::Flag::QUEST_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:
return "NO_ACCESS_TO_QUEST";
default:
throw runtime_error("invalid drop mode");
throw std::runtime_error("invalid drop mode");
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItemManager {
phosg::PrefixedLogger log;
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::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
+2 -4
View File
@@ -2,8 +2,6 @@
#include <phosg/Strings.hh>
using namespace std;
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_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(
phosg::PrefixedLogger& log, const phosg::JSON& d, const char* json_key) {
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);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
}
+7 -9
View File
@@ -2,8 +2,6 @@
#include "CommonFileFormats.hh"
using namespace std;
template <bool BE>
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,
@@ -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,
/* 20 */ 3, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4};
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];
}
@@ -196,7 +194,7 @@ public:
virtual const VectorXYZTF& get_color_rgba(size_t index) const {
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);
}
@@ -241,29 +239,29 @@ public:
return 0;
}
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 {
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 {
return 0;
}
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 {
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 {
return 0;
}
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 {
+514 -551
View File
File diff suppressed because it is too large Load Diff
+337 -343
View File
File diff suppressed because it is too large Load Diff
+24 -5
View File
@@ -456,7 +456,7 @@ public:
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
// non-random sections according to the challenge mode enemy generation algorithm.
std::shared_ptr<MapFile> materialize_random_sections(uint32_t random_seed);
@@ -766,10 +766,12 @@ public:
std::shared_ptr<const SuperMap::Enemy> super_ene;
enum Flag {
LAST_HIT_MASK = 0x0003,
EXP_GIVEN = 0x0004,
ITEM_DROPPED = 0x0008,
ALL_HITS_MASK_FIRST = 0x0010,
ALL_HITS_MASK = 0x00F0,
ITEM_DROPPED = 0x0004,
SHARED_EXP_GIVEN = 0x0008,
FULL_EXP_GIVEN_MASK_FIRST = 0x0010,
FULL_EXP_GIVEN_MASK = 0x00F0,
ALL_HITS_MASK_FIRST = 0x0100,
ALL_HITS_MASK = 0x0F00,
};
size_t e_id = 0;
size_t set_id = 0;
@@ -817,6 +819,23 @@ public:
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 {
return this->server_flags & (Flag::ALL_HITS_MASK_FIRST << client_id);
}
+5 -20
View File
@@ -1,28 +1,13 @@
#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(
uint32_t item_id,
const 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,
const std::string& name,
std::function<std::string()> get_description,
uint32_t flags)
: item_id(item_id),
name(name),
description(),
get_description(std::move(get_description)),
flags(flags) {}
: item_id(item_id), name(name), description(), get_description(std::move(get_description)), flags(flags) {}
Menu::Menu(uint32_t menu_id, const std::string& name)
: menu_id(menu_id),
name(name) {}
Menu::Menu(uint32_t menu_id, const std::string& name) : menu_id(menu_id), name(name) {}
+6 -8
View File
@@ -17,18 +17,16 @@
#include <phosg/Strings.hh>
#include <stdexcept>
using namespace std;
map<string, uint32_t> get_local_addresses() {
map<string, uint32_t> ret;
std::map<std::string, uint32_t> get_local_addresses() {
std::map<std::string, uint32_t> ret;
#ifndef PHOSG_WINDOWS
struct ifaddrs* ifa_raw;
if (getifaddrs(&ifa_raw)) {
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) {
if (!i->ifa_addr) {
@@ -56,7 +54,7 @@ map<string, uint32_t> get_local_addresses() {
}
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) {
@@ -92,7 +90,7 @@ bool is_local_address(const sockaddr_storage& daddr) {
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("{}.{}.{}.{}",
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
+7 -9
View File
@@ -10,8 +10,6 @@
#include "Compression.hh"
#include "Text.hh"
using namespace std;
struct Entry {
pstring<TextEncoding::ASCII, 0x80> filename;
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) {
if (password.size() > 0xFF) {
throw runtime_error("password is too long");
throw std::runtime_error("password is too long");
}
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();
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;) {
uint32_t size = r.pget_u32l(offset) ^ 0x12345678;
uint32_t entry_offset = offset - size;
const auto& entry = r.pget<Entry>(entry_offset);
string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
string filename = entry.filename.decode();
std::string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
std::string filename = entry.filename.decode();
decrypt_ppk_data(data, phosg::tolower(filename), password);
uint32_t checksum = phosg::crc32(data.data(), data.size());
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));
}
if (entry.compressed_size < entry.decompressed_size) {
data = prs_decompress(data);
}
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;
}
+33 -35
View File
@@ -9,8 +9,6 @@
#include <stdexcept>
#include <string>
using namespace std;
RandomGenerator::RandomGenerator(uint32_t seed) : initial_seed(seed) {}
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) {
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;
@@ -185,7 +183,7 @@ PSOBBEncryption::PSOBBEncryption(const KeyFile& key, const void* original_seed,
void PSOBBEncryption::encrypt(void* vdata, size_t size) {
if (this->state.subtype == Subtype::TFS1) {
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);
@@ -212,7 +210,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
} else if (this->state.subtype == Subtype::JSD1) {
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);
for (size_t z = 0; z < size; z++) {
@@ -230,7 +228,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
} else { // STANDARD or MOCB1
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;
@@ -274,7 +272,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
void PSOBBEncryption::decrypt(void* vdata, size_t size) {
if (this->state.subtype == Subtype::TFS1) {
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);
@@ -301,7 +299,7 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) {
} else if (this->state.subtype == Subtype::JSD1) {
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);
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
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;
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) {
// 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?)
string seed;
std::string seed;
const uint8_t* original_seed_data = reinterpret_cast<const uint8_t*>(original_seed);
for (size_t x = 0; x < seed_size; x += 3) {
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)
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) {
@@ -641,27 +639,27 @@ PSOV2OrV3DetectorEncryption::PSOV2OrV3DetectorEncryption(
void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size) {
if (!this->active_crypt) {
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 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));
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));
bool v2_match = this->v2_matches.count(decrypted_v2);
bool v3_match = this->v3_matches.count(decrypted_v3);
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})",
encrypted, decrypted_v2, decrypted_v3));
} 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) {
this->active_crypt = std::move(v2_crypt);
*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 {
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();
}
@@ -689,11 +687,11 @@ void PSOV2OrV3ImitatorEncryption::encrypt(void* data, size_t size) {
if (!this->active_crypt) {
auto t = this->detector_crypt->type();
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) {
this->active_crypt = make_shared<PSOV3Encryption>(this->key);
this->active_crypt = std::make_shared<PSOV3Encryption>(this->key);
} 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);
@@ -707,8 +705,8 @@ PSOEncryption::Type PSOV2OrV3ImitatorEncryption::type() const {
}
PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
const vector<shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const unordered_set<string>& expected_first_data,
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
const std::unordered_set<std::string>& expected_first_data,
const void* seed,
size_t seed_size)
: possible_keys(possible_keys),
@@ -717,7 +715,7 @@ PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size) {
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);
}
@@ -729,13 +727,13 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
}
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) {
this->active_key = key;
this->active_crypt = make_shared<PSOBBEncryption>(*this->active_key, this->seed.data(), this->seed.size());
string test_data(reinterpret_cast<const char*>(data), size);
this->active_crypt = std::make_shared<PSOBBEncryption>(*this->active_key, this->seed.data(), this->seed.size());
std::string test_data(reinterpret_cast<const char*>(data), size);
this->active_crypt->decrypt(test_data.data(), test_data.size());
if (this->expected_first_data.count(test_data)) {
memcpy(data, test_data.data(), size);
@@ -744,7 +742,7 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
this->active_key.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 {
@@ -752,7 +750,7 @@ PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
}
PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption(
shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
const void* seed,
size_t seed_size,
bool jsd1_use_detector_seed)
@@ -772,19 +770,19 @@ PSOEncryption::Type PSOBBMultiKeyImitatorEncryption::type() const {
return Type::BB;
}
shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
std::shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
if (!this->active_crypt.get()) {
auto key = this->detector_crypt->get_active_key();
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
// 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) {
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 {
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;
@@ -836,7 +834,7 @@ static uint8_t count_one_bits(uint16_t v) {
}
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;
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);
}
string decrypt_v2_registry_value(const void* data, size_t size) {
string ret(reinterpret_cast<const char*>(data), size);
std::string decrypt_v2_registry_value(const void* data, size_t size) {
std::string ret(reinterpret_cast<const char*>(data), size);
PSOV2Encryption crypt(0x66);
for (size_t z = 0; z < size; z++) {
ret[z] ^= (crypt.next() & 0x7F);
+3 -4
View File
@@ -421,12 +421,11 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size,
w.put<U32T<BE>>(seed);
w.write(data);
std::string ret = std::move(w.str());
PSOV2Encryption crypt(seed);
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 {
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
View File
@@ -2,8 +2,6 @@
#include "Text.hh"
using namespace std;
struct TObjectVTable {
phosg::be_uint32_t unused_a1;
phosg::be_uint32_t unused_a2;
@@ -24,19 +22,19 @@ struct TObject {
phosg::be_uint32_t vtable_addr;
} __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);
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 {
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);
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->destroy_addr = vt.destroy;
ret->update_addr = vt.update;
@@ -45,16 +43,16 @@ shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::
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 {
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);
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->flags = obj.flags;
ret->type_name = std::move(type_name);
+7 -9
View File
@@ -5,8 +5,6 @@
#include "Text.hh"
using namespace std;
extern bool use_terminal_colors;
PSOCommandHeader::PSOCommandHeader() {
@@ -37,7 +35,7 @@ uint16_t PSOCommandHeader::command(Version version) const {
case Version::BB_V4:
return this->bb.command;
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;
break;
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:
return this->bb.size;
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;
break;
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:
return this->bb.flag;
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;
break;
default:
throw logic_error("unknown game version");
throw std::logic_error("unknown game version");
}
}
@@ -255,7 +253,7 @@ std::string prepend_command_header(
}
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);
return std::move(ret.str());
+9 -11
View File
@@ -22,8 +22,6 @@
#include "ReceiveSubcommands.hh"
#include "SendCommands.hh"
using namespace std;
PatchDownloadSession::PatchDownloadSession(
std::shared_ptr<asio::io_context> io_context,
const std::string& remote_host,
@@ -54,9 +52,9 @@ PatchDownloadSession::PatchDownloadSession(
}
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);
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->io_context,
std::move(sock),
@@ -79,14 +77,14 @@ void PatchDownloadSession::check_path_token(const std::string& token) {
if (token == "..") {
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");
}
}
std::string PatchDownloadSession::resolve_filename(const std::string& filename) const {
check_path_token(filename);
string path = this->output_dir;
std::string path = this->output_dir;
for (const auto& dir_name : this->dir_path) {
path.push_back('/');
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") {
throw std::runtime_error("incorrect copyright message");
}
this->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
this->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
this->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
this->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
this->channel->send(0x02);
this->log.info_f("Enabled encryption");
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>();
string dirname = cmd.name.decode();
std::string dirname = cmd.name.decode();
check_path_token(dirname);
this->dir_path.emplace_back(std::move(dirname));
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>();
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);
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 new_channel = SocketChannel::create(
+24 -31
View File
@@ -12,8 +12,6 @@
#include "Loggers.hh"
using namespace std;
int64_t file_mtime_int(const std::string& path) {
auto mtime = std::filesystem::last_write_time(path);
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();
}
PatchFileIndex::File::File(PatchFileIndex* index)
: index(index),
crc32(0),
size(0) {}
PatchFileIndex::File::File(PatchFileIndex* index) : index(index), crc32(0), size(0) {}
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
if (!this->loaded_data) {
string relative_path = phosg::join(this->path_directories, "/") + "/" + this->name;
string full_path = this->index->root_dir + "/" + relative_path;
std::string relative_path = phosg::join(this->path_directories, "/") + "/" + this->name;
std::string full_path = this->index->root_dir + "/" + 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();
}
return this->loaded_data;
}
PatchFileIndex::PatchFileIndex(const string& root_dir)
: root_dir(root_dir) {
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
PatchFileIndex::PatchFileIndex(const std::string& root_dir) : root_dir(root_dir) {
std::string metadata_cache_filename = root_dir + "/.metadata-cache.json";
phosg::JSON metadata_cache_json;
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);
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();
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;
phosg::JSON new_metadata_cache_json = phosg::JSON::dict();
vector<string> path_directories;
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
std::vector<std::string> path_directories;
std::function<void(const std::string&)> collect_dir = [&](const std::string& dir) -> void {
path_directories.emplace_back(dir);
string relative_dirs = phosg::join(path_directories, "/");
string full_dir_path = root_dir + '/' + relative_dirs;
std::string relative_dirs = phosg::join(path_directories, "/");
std::string full_dir_path = root_dir + '/' + relative_dirs;
patch_index_log.debug_f("Listing directory {}", 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)
if (item.starts_with(".")) {
continue;
}
string relative_item_path = relative_dirs + '/' + item;
string full_item_path = root_dir + '/' + relative_item_path;
std::string relative_item_path = relative_dirs + '/' + item;
std::string full_item_path = root_dir + '/' + relative_item_path;
if (std::filesystem::is_directory(full_item_path)) {
collect_dir(item);
} 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->name = item;
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;
try {
cache_item_json = metadata_cache_json.at(relative_item_path);
uint64_t cached_size = cache_item_json.get_int(0);
int64_t cached_mtime = cache_item_json.get_int(1);
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) {
throw runtime_error("file size has changed");
throw std::runtime_error("file size has changed");
}
f->size = cached_size;
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());
}
} catch (const exception& e) {
} catch (const std::exception& e) {
compute_crc32s_message = e.what();
}
@@ -110,7 +103,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
auto data = f->load_data(); // Sets f->size
f->crc32 = phosg::crc32(data->data(), f->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));
}
@@ -149,7 +142,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
try {
phosg::save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
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());
}
} 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;
}
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);
}
+2 -1
View File
@@ -8,7 +8,8 @@
#include <unordered_map>
#include <vector>
struct PatchFileIndex {
class PatchFileIndex {
public:
static constexpr size_t CHUNK_SIZE = 0x6000;
explicit PatchFileIndex(const std::string& root_dir);
-1
View File
@@ -12,7 +12,6 @@
#include <vector>
#include "ChoiceSearch.hh"
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "PSOEncryption.hh"
#include "Text.hh"
+41 -43
View File
@@ -20,28 +20,26 @@
#include "Text.hh"
#include "Version.hh"
using namespace std;
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
this->visual.name_color = pre.visual.name_color;
this->visual.extra_model = pre.visual.extra_model;
this->visual.name_color_checksum = pre.visual.name_color_checksum;
this->visual.section_id = pre.visual.section_id;
this->visual.char_class = pre.visual.char_class;
this->visual.validation_flags = pre.visual.validation_flags;
this->visual.version = pre.visual.version;
this->visual.class_flags = pre.visual.class_flags;
this->visual.costume = pre.visual.costume;
this->visual.skin = pre.visual.skin;
this->visual.face = pre.visual.face;
this->visual.head = pre.visual.head;
this->visual.hair = pre.visual.hair;
this->visual.hair_r = pre.visual.hair_r;
this->visual.hair_g = pre.visual.hair_g;
this->visual.hair_b = pre.visual.hair_b;
this->visual.proportion_x = pre.visual.proportion_x;
this->visual.proportion_y = pre.visual.proportion_y;
this->name = pre.name;
void PlayerVisualConfigV4::apply_dressing_room(const PlayerVisualConfigV4& new_visual) {
this->sh.name_color = new_visual.sh.name_color;
this->sh.extra_model = new_visual.sh.extra_model;
this->sh.name_color_checksum = new_visual.sh.name_color_checksum;
this->sh.section_id = new_visual.sh.section_id;
this->sh.char_class = new_visual.sh.char_class;
this->sh.validation_flags = new_visual.sh.validation_flags;
this->sh.version = new_visual.sh.version;
this->sh.class_flags = new_visual.sh.class_flags;
this->sh.costume = new_visual.sh.costume;
this->sh.skin = new_visual.sh.skin;
this->sh.face = new_visual.sh.face;
this->sh.head = new_visual.sh.head;
this->sh.hair = new_visual.sh.hair;
this->sh.hair_r = new_visual.sh.hair_r;
this->sh.hair_g = new_visual.sh.hair_g;
this->sh.hair_b = new_visual.sh.hair_b;
this->sh.proportion_x = new_visual.sh.proportion_x;
this->sh.proportion_y = new_visual.sh.proportion_y;
this->name = new_visual.name;
}
void GuildCardBB::clear() {
@@ -414,7 +412,7 @@ const char* phosg::name_for_enum<BattleRules::TechDiskMode>(BattleRules::TechDis
case BattleRules::TechDiskMode::LIMIT_LEVEL:
return "LIMIT_LEVEL";
default:
throw invalid_argument("invalid BattleRules::TechDiskMode value");
throw std::invalid_argument("invalid BattleRules::TechDiskMode value");
}
}
template <>
@@ -426,7 +424,7 @@ BattleRules::TechDiskMode phosg::enum_for_name<BattleRules::TechDiskMode>(const
} else if (!strcmp(name, "LIMIT_LEVEL")) {
return BattleRules::TechDiskMode::LIMIT_LEVEL;
} 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:
return "FORBID_RARES";
default:
throw invalid_argument("invalid BattleRules::WeaponAndArmorMode value");
throw std::invalid_argument("invalid BattleRules::WeaponAndArmorMode value");
}
}
template <>
@@ -456,7 +454,7 @@ BattleRules::WeaponAndArmorMode phosg::enum_for_name<BattleRules::WeaponAndArmor
} else if (!strcmp(name, "FORBID_RARES")) {
return BattleRules::WeaponAndArmorMode::FORBID_RARES;
} 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:
return "FORBID_ALL";
default:
throw invalid_argument("invalid BattleRules::MagMode value");
throw std::invalid_argument("invalid BattleRules::MagMode value");
}
}
template <>
@@ -478,7 +476,7 @@ BattleRules::MagMode phosg::enum_for_name<BattleRules::MagMode>(const char* name
} else if (!strcmp(name, "FORBID_ALL")) {
return BattleRules::MagMode::FORBID_ALL;
} 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:
return "FORBID_ALL";
default:
throw invalid_argument("invalid BattleRules::ToolMode value");
throw std::invalid_argument("invalid BattleRules::ToolMode value");
}
}
template <>
@@ -504,7 +502,7 @@ BattleRules::ToolMode phosg::enum_for_name<BattleRules::ToolMode>(const char* na
} else if (!strcmp(name, "FORBID_ALL")) {
return BattleRules::ToolMode::FORBID_ALL;
} 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:
return "ALL_PLAYERS";
default:
throw invalid_argument("invalid BattleRules::TrapMode value");
throw std::invalid_argument("invalid BattleRules::TrapMode value");
}
}
template <>
@@ -526,7 +524,7 @@ BattleRules::TrapMode phosg::enum_for_name<BattleRules::TrapMode>(const char* na
} else if (!strcmp(name, "ALL_PLAYERS")) {
return BattleRules::TrapMode::ALL_PLAYERS;
} 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:
return "CLEAR_AND_ALLOW";
default:
throw invalid_argument("invalid BattleRules::MesetaDropMode value");
throw std::invalid_argument("invalid BattleRules::MesetaDropMode value");
}
}
template <>
@@ -552,7 +550,7 @@ BattleRules::MesetaMode phosg::enum_for_name<BattleRules::MesetaMode>(const char
} else if (!strcmp(name, "CLEAR_AND_ALLOW")) {
return BattleRules::MesetaMode::CLEAR_AND_ALLOW;
} 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:
return "LIMIT_LIVES";
default:
throw invalid_argument("invalid BattleRules::MesetaDropMode value");
throw std::invalid_argument("invalid BattleRules::MesetaDropMode value");
}
}
template <>
@@ -578,7 +576,7 @@ BattleRules::RespawnMode phosg::enum_for_name<BattleRules::RespawnMode>(const ch
} else if (!strcmp(name, "LIMIT_LIVES")) {
return BattleRules::RespawnMode::LIMIT_LIVES;
} 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) {
// 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)}, {}},
{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)}, {}},
@@ -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)}, {}},
{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)}, {}},
{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)}, {}},
@@ -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)}, {}},
{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}}},
{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}}},
@@ -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}} },
});
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)}, {}},
{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)}, {}},
@@ -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)}, {}},
{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)}, {}},
{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)}, {}},
@@ -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)}, {}},
{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}}},
{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}}},
@@ -716,7 +714,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
} else if ((class_flags & 0xE0) == 0x80) {
return is_v1_or_v2(version) ? v2_force_templates.at(index) : v3_force_templates.at(index);
} else {
throw runtime_error("invalid class flags on original player");
throw std::runtime_error("invalid class flags on original player");
}
}
+120 -82
View File
@@ -12,7 +12,6 @@
#include "ChoiceSearch.hh"
#include "CommonFileFormats.hh"
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "LevelTable.hh"
#include "PSOEncryption.hh"
@@ -24,64 +23,64 @@
class Client;
class ItemParameterTable;
struct PlayerDispDataBB;
struct PlayerDispDataV4;
struct PlayerVisualConfigV4;
template <bool BE>
struct PlayerVisualConfigT {
/* 00 */ pstring<TextEncoding::ASCII, 0x10> name;
/* 10 */ parray<uint8_t, 8> unknown_a2;
/* 18 */ U32T<BE> name_color = 0xFFFFFFFF; // ARGB
/* 1C */ uint8_t extra_model = 0;
struct PlayerVisualConfigSharedT {
/* 00 */ parray<uint8_t, 8> unknown_a2;
/* 08 */ U32T<BE> name_color = 0xFFFFFFFF; // ARGB
/* 0C */ 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
// 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.
/* 1D */ uint8_t npc_saved_data_type = 0;
/* 1E */ uint8_t npc_saved_costume = 0;
/* 1F */ uint8_t npc_saved_skin = 0;
/* 20 */ uint8_t npc_saved_face = 0;
/* 21 */ uint8_t npc_saved_head = 0;
/* 22 */ uint8_t npc_saved_hair = 0;
/* 23 */ uint8_t npc_saved_hair_r = 0;
/* 24 */ uint8_t npc_saved_hair_g = 0;
/* 25 */ uint8_t npc_saved_hair_b = 0;
/* 26 */ parray<uint8_t, 2> unused;
/* 28 */ F32T<BE> npc_saved_proportion_y = 0.0;
/* 0D */ uint8_t npc_saved_data_type = 0;
/* 0E */ uint8_t npc_saved_costume = 0;
/* 0F */ uint8_t npc_saved_skin = 0;
/* 10 */ uint8_t npc_saved_face = 0;
/* 11 */ uint8_t npc_saved_head = 0;
/* 12 */ uint8_t npc_saved_hair = 0;
/* 13 */ uint8_t npc_saved_hair_r = 0;
/* 14 */ uint8_t npc_saved_hair_g = 0;
/* 15 */ uint8_t npc_saved_hair_b = 0;
/* 16 */ parray<uint8_t, 2> unused;
/* 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
// 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.
/* 2C */ U32T<BE> name_color_checksum = 0;
/* 30 */ uint8_t section_id = 0;
/* 31 */ uint8_t char_class = 0;
/* 1C */ U32T<BE> name_color_checksum = 0;
/* 20 */ uint8_t section_id = 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:
// -----FCS
// F = class_flags is incorrect for the character's char_class value
// C = char_class is out of range
// S = section_id is out of range
/* 32 */ uint8_t validation_flags = 0;
/* 33 */ uint8_t version = 0;
/* 22 */ uint8_t validation_flags = 0;
/* 23 */ uint8_t version = 0;
// class_flags specifies features of the character's class. The bits are:
// -------- -------- -------- FRHANMfm
// F = force, R = ranger, H = hunter
// A = android, N = newman, M = human
// 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
// D = Dark attribute
// M = Machine attribute
// A = Altered Beast attribute
// N = Native attribute
/* 34 */ U32T<BE> class_flags = 0;
/* 38 */ U16T<BE> costume = 0;
/* 3A */ U16T<BE> skin = 0;
/* 3C */ U16T<BE> face = 0;
/* 3E */ U16T<BE> head = 0;
/* 40 */ U16T<BE> hair = 0;
/* 42 */ U16T<BE> hair_r = 0;
/* 44 */ U16T<BE> hair_g = 0;
/* 46 */ U16T<BE> hair_b = 0;
/* 48 */ F32T<BE> proportion_x = 0.0;
/* 4C */ F32T<BE> proportion_y = 0.0;
/* 50 */
/* 24 */ U32T<BE> class_flags = 0;
/* 28 */ U16T<BE> costume = 0;
/* 2A */ U16T<BE> skin = 0;
/* 2C */ U16T<BE> face = 0;
/* 2E */ U16T<BE> head = 0;
/* 30 */ U16T<BE> hair = 0;
/* 32 */ U16T<BE> hair_r = 0;
/* 34 */ U16T<BE> hair_g = 0;
/* 36 */ U16T<BE> hair_b = 0;
/* 38 */ F32T<BE> proportion_x = 0.0;
/* 3C */ F32T<BE> proportion_y = 0.0;
/* 40 */
static uint32_t compute_name_color_checksum(uint32_t name_color) {
uint8_t x = (phosg::random_object<uint32_t>() % 0xFF) + 1;
@@ -284,12 +283,10 @@ struct PlayerVisualConfigT {
this->name_color_checksum = 0;
}
this->class_flags = class_flags_for_class(this->char_class);
this->name.clear_after_bytes(0x0C);
}
operator PlayerVisualConfigT<!BE>() const {
PlayerVisualConfigT<!BE> ret;
ret.name = this->name;
operator PlayerVisualConfigSharedT<!BE>() const {
PlayerVisualConfigSharedT<!BE> ret;
ret.unknown_a2 = this->unknown_a2;
ret.name_color = this->name_color;
ret.extra_model = this->extra_model;
@@ -312,14 +309,65 @@ struct PlayerVisualConfigT {
ret.proportion_y = this->proportion_y;
return ret;
}
} __packed_ws_be__(PlayerVisualConfigT, 0x50);
using PlayerVisualConfig = PlayerVisualConfigT<false>;
using PlayerVisualConfigBE = PlayerVisualConfigT<true>;
} __packed_ws_be__(PlayerVisualConfigSharedT, 0x40);
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;
/* 24 */ PlayerVisualConfigT<BE> visual;
/* 24 */ PlayerVisualConfigV123T<BE> visual;
/* 74 */ parray<uint8_t, 0x48> config;
/* BC */ parray<uint8_t, 0x14> technique_levels_v1;
/* D0 */
@@ -328,60 +376,50 @@ struct PlayerDispDataDCPCV3T {
this->visual.enforce_lobby_join_limits_for_version(v);
}
PlayerDispDataBB to_bb(Language to_language, Language from_language) const;
} __packed_ws_be__(PlayerDispDataDCPCV3T, 0xD0);
using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T<false>;
using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T<true>;
PlayerDispDataV4 to_v4(Language to_language, Language from_language) const;
} __packed_ws_be__(PlayerDispDataV123T, 0xD0);
using PlayerDispDataV123 = PlayerDispDataV123T<false>;
using PlayerDispDataV123BE = PlayerDispDataV123T<true>;
struct PlayerDispDataBBPreview {
struct PlayerDispDataV4Preview {
/* 00 */ le_uint32_t exp = 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
// char array and this is BB)
/* 08 */ PlayerVisualConfig visual;
/* 58 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
/* 08 */ PlayerVisualConfigV4 visual;
/* 78 */ uint32_t play_time_seconds = 0;
/* 7C */
} __packed_ws__(PlayerDispDataBBPreview, 0x7C);
} __packed_ws__(PlayerDispDataV4Preview, 0x7C);
// BB player appearance and stats data
struct PlayerDispDataBB {
struct PlayerDispDataV4 {
/* 0000 */ PlayerStats stats;
/* 0024 */ PlayerVisualConfig visual;
/* 0074 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> name;
/* 0024 */ PlayerVisualConfigV4 visual;
/* 0094 */ parray<uint8_t, 0xE8> config;
/* 017C */ parray<uint8_t, 0x14> technique_levels_v1;
/* 0190 */
void enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
this->name.clear_after_bytes(0x18); // 12 characters
this->visual.sh.enforce_lobby_join_limits_for_version(v);
this->visual.name.clear_after_bytes(0x18); // 12 characters
}
template <bool BE>
PlayerDispDataDCPCV3T<BE> to_dcpcv3(Language to_language, Language from_language) const {
PlayerDispDataDCPCV3T<BE> ret;
PlayerDispDataV123T<BE> to_v123(Language to_language, Language from_language) const {
PlayerDispDataV123T<BE> ret;
ret.stats = this->stats;
ret.visual = this->visual;
std::string decoded_name = this->name.decode(from_language);
ret.visual.name.encode(decoded_name, to_language);
ret.visual = this->visual.to_v123<BE>(to_language, from_language);
ret.config = this->config;
ret.technique_levels_v1 = this->technique_levels_v1;
return ret;
}
void apply_preview(const PlayerDispDataBBPreview&);
void apply_dressing_room(const PlayerDispDataBBPreview&);
} __packed_ws__(PlayerDispDataBB, 0x190);
} __packed_ws__(PlayerDispDataV4, 0x190);
template <bool BE>
PlayerDispDataBB PlayerDispDataDCPCV3T<BE>::to_bb(Language to_language, Language from_language) const {
PlayerDispDataBB bb;
PlayerDispDataV4 PlayerDispDataV123T<BE>::to_v4(Language to_language, Language from_language) const {
PlayerDispDataV4 bb;
bb.stats = this->stats;
bb.visual = this->visual;
bb.visual.name.encode(" 0");
std::string decoded_name = this->visual.name.decode(from_language);
bb.name.encode(decoded_name, to_language);
bb.visual = this->visual.to_v4(to_language, from_language);
bb.config = this->config;
bb.technique_levels_v1 = this->technique_levels_v1;
return bb;
@@ -835,25 +873,25 @@ DestT convert_player_disp_data(const SrcT&, Language, Language) {
}
template <>
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3>(
const PlayerDispDataDCPCV3& src, Language, Language) {
inline PlayerDispDataV123 convert_player_disp_data<PlayerDispDataV123>(
const PlayerDispDataV123& src, Language, Language) {
return src;
}
template <>
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(
const PlayerDispDataBB& src, Language to_language, Language from_language) {
return src.to_dcpcv3<false>(to_language, from_language);
inline PlayerDispDataV123 convert_player_disp_data<PlayerDispDataV123, PlayerDispDataV4>(
const PlayerDispDataV4& src, Language to_language, Language from_language) {
return src.to_v123<false>(to_language, from_language);
}
template <>
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataDCPCV3>(
const PlayerDispDataDCPCV3& src, Language to_language, Language from_language) {
return src.to_bb(to_language, from_language);
inline PlayerDispDataV4 convert_player_disp_data<PlayerDispDataV4, PlayerDispDataV123>(
const PlayerDispDataV123& src, Language to_language, Language from_language) {
return src.to_v4(to_language, from_language);
}
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;
}
+143 -144
View File
@@ -29,17 +29,15 @@
#include "ReceiveSubcommands.hh"
#include "SendCommands.hh"
using namespace std;
enum class HandlerResult {
FORWARD = 0,
SUPPRESS,
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;
if (!ch || !ch->connected()) {
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
// 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;
}
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");
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: {:02X} {:02X}", msg.command, msg.flag);
c->proxy_session->server_channel->disconnect();
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) {
uint64_t ping_usecs = phosg::now() - c->ping_start_time;
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;
}
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
// client to the proxy server.
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));
}
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,
// anything else should end the session)
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;
}
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) {
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
@@ -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
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_out = make_shared<PSOV3Encryption>(cmd.client_key);
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
} else {
c->proxy_session->server_channel->crypt_in = 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_in = std::make_shared<PSOV2Encryption>(cmd.server_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
@@ -267,7 +265,7 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
case Version::DC_NTE:
// 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_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
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: {
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:
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:
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;
ret.username.encode(c->username);
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;
}
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
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 client can't do this since it sends the first encrypted data on the connection.
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->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_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;
}
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);
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
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;
}
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
if (msg.data.size() < offsetof(S_UpdateClientConfig_V3_04, client_config)) {
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) {
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);
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);
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(),
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,
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()));
// 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;
}
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;
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);
@@ -452,7 +450,7 @@ static asio::awaitable<HandlerResult> S_V123_06(shared_ptr<Client> c, Channel::M
}
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) {
auto& cmd = msg.check_size_t<CmdT>();
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>;
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;
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
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_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
// versions probably also will crash)
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;
}
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
c->proxy_session->server_channel->send(0x99, 0x00);
co_return HandlerResult::SUPPRESS;
}
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);
if (cmd.code_size && c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
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)) {
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();
PSOV2Encryption crypt(key);
string decrypted_data;
std::string decrypted_data;
if (is_big_endian) {
phosg::StringWriter w;
while (!r.eof()) {
@@ -566,7 +564,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
if (decompressed_size < code.size()) {
code.resize(decompressed_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 {
@@ -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);
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) {
try {
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);
@@ -596,7 +594,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
phosg::StringReader r(code.data(), code.size());
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);
uint32_t reloc_offset = 0;
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(r.pget<U32T<BE>>(footer.root_offset), "start");
string disassembly;
std::string disassembly;
if (is_ppc) {
disassembly = ResourceDASM::PPC32Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
} 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);
} else {
// 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);
@@ -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);
} catch (const exception& e) {
} catch (const std::exception& e) {
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>();
shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>> promise;
std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>> promise;
if (!c->function_call_response_queue.empty()) {
promise = std::move(c->function_call_response_queue.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;
c->proxy_session->bb_client_sent_E0 = true;
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)) {
const auto& cmd = msg.check_size_t<S_SyncSystemFile_BB_E2>();
uint64_t ts = phosg::now();
string system_filename = std::format("system.{}.psosys", ts);
string team_membership_filename = std::format("system.{}.psosysteam", ts);
std::string system_filename = std::format("system.{}.psosys", ts);
std::string team_membership_filename = std::format("system.{}.psosysteam", ts);
phosg::save_object_file(system_filename, cmd.system_file);
phosg::save_object_file(team_membership_filename, cmd.team_membership);
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;
}
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)) {
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");
PSOCommandHeaderBB header = {msg.data.size() + sizeof(PSOCommandHeaderBB), msg.command, msg.flag};
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;
}
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)) {
const auto& cmd = msg.check_size_t<S_GuildCardFileChunk_02DC>(8, sizeof(S_GuildCardFileChunk_02DC));
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;
}
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)) {
const auto& cmd = msg.check_size_t<C_GuildCardDataRequest_BB_03DC>();
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);
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;
}
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 (msg.command == 0x01EB) {
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);
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()) {
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) {
std::string filename = entry.filename.decode();
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>
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;
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
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_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>();
bool modified = false;
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;
}
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'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
@@ -836,7 +834,7 @@ static asio::awaitable<HandlerResult> S_B_22(shared_ptr<Client> c, Channel::Mess
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
// 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
@@ -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
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);
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
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;
}
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
// 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)) {
@@ -907,7 +905,7 @@ static asio::awaitable<HandlerResult> S_V3_1A_D5(shared_ptr<Client> c, Channel::
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
if (c->version() == Version::GC_NTE) {
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) {
co_return HandlerResult::FORWARD;
}
@@ -947,7 +945,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
case ProxyDropMode::INTERCEPT:
break;
default:
throw logic_error("invalid drop mode");
throw std::logic_error("invalid drop mode");
}
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");
} else {
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);
res.item.id = c->proxy_session->next_item_id++;
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;
}
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();
if (msg.data.size() < 4) {
@@ -1054,7 +1052,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
case 0x46: {
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");
co_return HandlerResult::SUPPRESS;
}
@@ -1063,7 +1061,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
case 0x47: {
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");
co_return HandlerResult::SUPPRESS;
}
@@ -1072,7 +1070,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
case 0x49: {
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");
co_return HandlerResult::SUPPRESS;
}
@@ -1094,10 +1092,10 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
case 0x6A: {
auto& cmd = msg.check_size_t<G_SetBossWarpFlags_6x6A>();
if (c->proxy_session->map_state) {
shared_ptr<MapState::ObjectState> obj_st;
std::shared_ptr<MapState::ObjectState> obj_st;
try {
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());
}
@@ -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);
if (header.subsubcommand == 0x00000041) {
const auto& cmd = msg.check_size_t<G_MapData_Ep3_6xB6x41>(0xFFFF);
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 filename = std::format("map{:08X}.{}.mnmd", cmd.map_number, phosg::now());
std::string map_data = prs_decompress(msg.data.data() + sizeof(cmd), msg.data.size() - sizeof(cmd));
phosg::save_file(filename, map_data);
if ((map_data.size() != sizeof(Episode3::MapDefinition)) &&
(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;
}
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;
// 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;
}
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);
msg.data = add_color(msg.data);
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;
}
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 {
phosg::strip_trailing_zeroes(msg.data);
if (msg.data.size() & 1) {
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);
msg.data = tt_utf8_to_utf16(decoded.data(), decoded.size());
while (msg.data.size() & 3) {
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());
}
// 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>
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>();
string filename = cmd.filename.decode();
string output_filename;
std::string filename = cmd.filename.decode();
std::string output_filename;
bool is_download = (msg.command == 0xA6);
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
size_t extension_offset = filename.rfind('.');
string basename, extension;
if (extension_offset != string::npos) {
std::string basename, extension;
if (extension_offset != std::string::npos) {
basename = filename.substr(0, extension_offset);
extension = filename.substr(extension_offset);
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_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>();
bool modified = false;
ProxySession::SavingFile* sf = nullptr;
try {
sf = &c->proxy_session->saving_files.at(cmd.filename.decode());
} catch (const out_of_range&) {
string filename = cmd.filename.decode();
} catch (const std::out_of_range&) {
std::string filename = cmd.filename.decode();
c->log.warning_f("Received data for non-open file {}", filename);
}
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);
size_t block_offset = msg.flag * 0x400;
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;
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->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")) {
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) {
@@ -1446,23 +1444,23 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
c->version(),
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);
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;
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->proxy_session->lobby_difficulty,
c->proxy_session->lobby_event,
c->proxy_session->lobby_random_seed,
MapState::DEFAULT_RARE_ENEMIES,
make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
std::make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
supermap);
} catch (const exception& e) {
} catch (const std::exception& e) {
c->log.warning_f("Failed to load quest map: {}", e.what());
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;
}
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 (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
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 (msg.data.size() < 4) {
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;
}
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));
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;
}
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)) {
try {
const auto& header = msg.check_size_t<S_UpdateMediaHeader_Ep3_B9>(0xFFFF);
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));
string output_filename = std::format("media-update.{}", phosg::now());
std::string output_filename = std::format("media-update.{}", phosg::now());
if (header.type == 1) {
output_filename += ".gvm";
} 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);
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());
}
}
@@ -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;
}
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) {
c->proxy_session->suppress_next_ep3_media_update_confirmation = false;
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;
}
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 (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);
@@ -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.
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)) {
auto& cmd = msg.check_size_t<S_MesetaTransaction_Ep3_BA>();
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;
}
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) {
c->proxy_session->leader_client_id = leader_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>
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) {
c->proxy_session->clear_lobby_players(12);
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()) {
c->log.warning_f("Ignoring invalid player index {} at position {}", index, x);
} 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)) {
num_replacements++;
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.name = name;
p.language = entry.inventory.language;
p.section_id = entry.disp.visual.section_id;
p.char_class = entry.disp.visual.char_class;
p.section_id = entry.disp.visual.sh.section_id;
p.char_class = entry.disp.visual.sh.char_class;
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>
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;
S_JoinGame_Ep3_64* cmd_ep3 = nullptr;
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 {
c->proxy_session->lobby_event = 0;
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_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_difficulty,
cmd->variations);
c->proxy_session->map_state = make_shared<MapState>(
c->proxy_session->map_state = std::make_shared<MapState>(
c->id,
c->proxy_session->lobby_difficulty,
c->proxy_session->lobby_event,
c->proxy_session->lobby_random_seed,
MapState::DEFAULT_RARE_ENEMIES,
make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
std::make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
supermaps);
} else {
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];
p.language = p_ep3.inventory.language;
p.name = p_ep3.disp.visual.name.decode(p.language);
p.section_id = p_ep3.disp.visual.section_id;
p.char_class = p_ep3.disp.visual.char_class;
p.section_id = p_ep3.disp.visual.sh.section_id;
p.char_class = p_ep3.disp.visual.sh.char_class;
} else {
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_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>();
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.language = player_entry.inventory.language;
p.name = player_entry.disp.visual.name.decode(p.language);
p.section_id = player_entry.disp.visual.section_id;
p.char_class = player_entry.disp.visual.char_class;
p.section_id = player_entry.disp.visual.sh.section_id;
p.char_class = player_entry.disp.visual.sh.char_class;
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;
}
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) {
co_return HandlerResult::SUPPRESS;
} 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
// command here.
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");
} else {
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)) {
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;
}
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->proxy_session->is_in_lobby = 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) {
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);
uint8_t private_flags = 0;
@@ -1978,7 +1976,7 @@ static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Messag
} else {
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());
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;
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
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>
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>();
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) {
@@ -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>;
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) {
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_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
// 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.
@@ -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;
size_t version_index = static_cast<size_t>(version);
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];
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);
try {
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) {
c->log.info_f("The preceding command from the {} was not forwarded", from_server ? "server" : "client");
} 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());
if (c->proxy_session && c->proxy_session->server_channel) {
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(
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;
// 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()) {
unique_ptr<Channel::Message> msg;
std::unique_ptr<Channel::Message> msg;
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) {
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];
@@ -2854,7 +2853,7 @@ asio::awaitable<void> handle_proxy_server_commands(
error_str = e.what();
}
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());
error_str = e.what();
channel->disconnect();
+4 -6
View File
@@ -2,11 +2,9 @@
#include "ServerState.hh"
using namespace std;
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) {
if (pc) {
this->remote_guild_card_number = pc->remote_guild_card_number;
@@ -22,11 +20,11 @@ ProxySession::~ProxySession() {
}
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;
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
this->item_creator = make_shared<ItemCreator>(
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
this->item_creator = std::make_shared<ItemCreator>(
s->common_item_set(version, nullptr),
s->rare_item_set(version, nullptr),
s->armor_random_set,
+224 -199
View File
File diff suppressed because it is too large Load Diff
+51 -52
View File
@@ -1,7 +1,5 @@
#include "QuestMetadata.hh"
using namespace std;
phosg::JSON QuestMetadata::FloorAssignment::json() const {
return phosg::JSON::dict({
{"Floor", this->floor},
@@ -21,51 +19,51 @@ std::string QuestMetadata::FloorAssignment::str() const {
void QuestMetadata::apply_json_overrides(const phosg::JSON& json) {
try {
this->description_flag = json.at("DescriptionFlag").as_int();
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->available_expression = make_shared<IntegralExpression>(json.get_string("AvailableIf"));
} catch (const out_of_range&) {
this->available_expression = std::make_shared<IntegralExpression>(json.get_string("AvailableIf"));
} catch (const std::out_of_range&) {
}
try {
this->enabled_expression = make_shared<IntegralExpression>(json.get_string("EnabledIf"));
} catch (const out_of_range&) {
this->enabled_expression = std::make_shared<IntegralExpression>(json.get_string("EnabledIf"));
} catch (const std::out_of_range&) {
}
try {
this->allow_start_from_chat_command = json.get_bool("AllowStartFromChatCommand");
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->joinable = json.get_bool("Joinable");
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->lock_status_register = json.get_int("LockStatusRegister");
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->enemy_exp_overrides = QuestMetadata::parse_enemy_exp_overrides(json.at("EnemyEXPOverrides"));
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->common_item_set_name = json.at("CommonItemSetName").as_string();
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->rare_item_set_name = json.at("RareItemSetName").as_string();
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
this->allowed_drop_modes = json.at("AllowedDropModes").as_int();
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
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 {
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 {
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));
}
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})",
this->category_id, other.category_id));
}
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: {})",
name_for_episode(this->episode), name_for_episode(other.episode)));
}
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: {})",
this->allow_start_from_chat_command ? "true" : "false",
other.allow_start_from_chat_command ? "true" : "false"));
}
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: {})",
this->joinable ? "true" : "false", other.joinable ? "true" : "false"));
}
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);
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: {})",
this->max_players, other.max_players));
}
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})",
this->lock_status_register, other.lock_status_register));
}
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) {
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() &&
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) {
if (!this_str.empty()) {
this_str += ", ";
@@ -139,32 +137,32 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
}
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));
}
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: {})",
this->battle_rules ? "present" : "absent", other.battle_rules ? "present" : "absent"));
}
if (this->battle_rules && (*this->battle_rules != *other.battle_rules)) {
string existing_str = this->battle_rules->json().serialize();
string new_str = other.battle_rules->json().serialize();
throw runtime_error(std::format(
std::string existing_str = this->battle_rules->json().serialize();
std::string new_str = other.battle_rules->json().serialize();
throw std::runtime_error(std::format(
"quest version has different battle rules (existing: {}, new: {})", existing_str, new_str));
}
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: {})",
this->challenge_template_index, other.challenge_template_index));
}
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: {})",
this->challenge_exp_multiplier, other.challenge_exp_multiplier));
}
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: {})",
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& other_fa = other.floor_assignments[z];
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: {})",
z, this_fa.str(), other_fa.str()));
}
}
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})",
this->description_flag, other.description_flag));
}
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: {})",
this->available_expression ? "present" : "absent", other.available_expression ? "present" : "absent"));
}
if (this->available_expression && *this->available_expression != *other.available_expression) {
string existing_str = this->available_expression->str();
string new_str = other.available_expression->str();
throw runtime_error(std::format(
std::string existing_str = this->available_expression->str();
std::string new_str = other.available_expression->str();
throw std::runtime_error(std::format(
"quest version has a different available expression (existing: {}, new: {})", existing_str, new_str));
}
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: {})",
this->enabled_expression ? "present" : "absent", other.enabled_expression ? "present" : "absent"));
}
if (this->enabled_expression && *this->enabled_expression != *other.enabled_expression) {
string existing_str = this->enabled_expression->str();
string new_str = other.enabled_expression->str();
throw runtime_error(std::format(
std::string existing_str = this->enabled_expression->str();
std::string new_str = other.enabled_expression->str();
throw std::runtime_error(std::format(
"quest version has a different enabled expression (existing: {}, new: {})", existing_str, new_str));
}
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: {})",
this->common_item_set_name, other.common_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: {})",
this->rare_item_set_name, other.rare_item_set_name));
}
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));
}
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)));
}
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: {})",
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"
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}});
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);
enemy_type = phosg::enum_for_name<EnemyType>(key_tokens[2]);
} else {
throw runtime_error("malformatted key: " + key);
throw std::runtime_error("malformatted key: " + key);
}
difficulty = difficulty_keys.at(key_tokens[0]);
if (floor == 0xFF) {
@@ -379,7 +378,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
}
return ret;
} catch (const exception& e) {
} catch (const std::exception& e) {
throw std::runtime_error(std::format("invalid enemy EXP overrides: ", e.what()));
}
}
+1 -1
View File
@@ -39,7 +39,7 @@ struct QuestMetadata {
std::string str() const;
};
uint32_t category_id = 0xFFFFFFFF;
uint32_t quest_number = 0xFFFFFFFF;
uint32_t quest_number = 0;
Episode episode = Episode::NONE;
std::array<FloorAssignment, 0x12> floor_assignments;
bool joinable = false;
+321 -300
View File
File diff suppressed because it is too large Load Diff
+66 -71
View File
@@ -9,18 +9,14 @@
#include "ItemData.hh"
#include "StaticGameData.hh"
using namespace std;
string RareItemSet::ExpandedDrop::str() const {
std::string RareItemSet::ExpandedDrop::str() const {
auto frac = phosg::reduce_fraction<uint64_t>(this->probability, 0x100000000);
auto hex = this->data.hex();
return std::format(
"({:08X} => {}/{}) {}",
this->probability, frac.first, frac.second, hex);
return std::format("({:08X} => {}/{}) {}", this->probability, frac.first, frac.second, hex);
}
string RareItemSet::ExpandedDrop::str(shared_ptr<const ItemNameIndex> name_index) const {
string ret = this->str();
std::string RareItemSet::ExpandedDrop::str(std::shared_ptr<const ItemNameIndex> name_index) const {
std::string ret = this->str();
ret += " (";
ret += name_index->describe_item(this->data);
ret += ")";
@@ -74,7 +70,7 @@ uint8_t RareItemSet::compress_rate(uint32_t probability) {
RareItemSet::ParsedRELData::PackedDrop::PackedDrop(const ExpandedDrop& exp)
: probability(RareItemSet::compress_rate(exp.probability)) {
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[1] = exp.data.data1[1];
@@ -182,7 +178,7 @@ RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
for (const auto& [enemy_type, specs] : collection.enemy_specs) {
const auto& def = type_definition_for_enemy(enemy_type);
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));
}
@@ -191,14 +187,14 @@ RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
if (dest_spec.data.empty()) {
dest_spec = spec;
} 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));
}
}
}
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 (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);
this->collections.emplace(
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) {
return std::format("ItemRT{}{}{}{}.rel",
((mode == GameMode::CHALLENGE) ? "c" : ""),
@@ -271,11 +267,11 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
for (size_t section_id = 0; section_id < 10; section_id++) {
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);
this->collections.emplace(
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
phosg::StringReader r(rel_data);
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);
this->collections.emplace(
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()) {
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}});
GameMode mode = mode_keys.at(mode_it.first);
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}});
Episode episode = episode_keys.at(episode_it.first);
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}});
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)];
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-")) {
uint8_t area_norm = FloorDefinition::get(episode, enemy_type_name.substr(4)).drop_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()) {
auto tokens = phosg::split(prob_desc.as_string(), '/');
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 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;
} else if (item_desc.is_string()) {
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());
} 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 {
vector<string> files;
std::vector<std::string> files;
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
if (is_v1 && (difficulty == Difficulty::ULTIMATE)) {
continue;
@@ -391,16 +387,16 @@ std::string RareItemSet::serialize_afs(bool is_v1) 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 (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
for (uint8_t section_id = 0; section_id < 10; section_id++) {
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));
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
}
}
@@ -410,10 +406,10 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
for (uint8_t section_id = 0; section_id < 10; section_id++) {
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));
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
}
}
@@ -421,17 +417,17 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
return GSLArchive::generate(files, big_endian);
}
string RareItemSet::serialize_html(
std::string RareItemSet::serialize_html(
GameMode mode,
Episode episode,
Difficulty difficulty,
shared_ptr<const ItemNameIndex> name_index,
shared_ptr<const CommonItemSet> common_item_set) const {
std::shared_ptr<const ItemNameIndex> name_index,
std::shared_ptr<const CommonItemSet> common_item_set) const {
struct ZoneTypes {
const char* name;
vector<uint8_t> floors;
vector<EnemyType> types;
std::vector<uint8_t> floors;
std::vector<EnemyType> types;
};
// clang-format off
@@ -561,7 +557,7 @@ string RareItemSet::serialize_html(
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("\
<html>\n\
<head>\n\
@@ -643,7 +639,7 @@ string RareItemSet::serialize_html(
</style>\n\
</head><body>\n");
string mode_token;
std::string mode_token;
switch (mode) {
case GameMode::NORMAL:
mode_token = "";
@@ -658,7 +654,7 @@ string RareItemSet::serialize_html(
mode_token = " (solo mode)";
break;
default:
throw logic_error("invalid game mode");
throw std::logic_error("invalid game mode");
}
blocks.emplace_back(std::format(
@@ -679,7 +675,7 @@ string RareItemSet::serialize_html(
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;
for (const auto& specs_list : specs_lists) {
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));
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"));
vector<string> tokens;
std::vector<std::string> tokens;
for (const auto& spec : specs_lists[section_id]) {
if (!tokens.empty()) {
tokens.emplace_back("");
@@ -705,7 +701,7 @@ string RareItemSet::serialize_html(
uint8_t dar = 0;
try {
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);
frac.first *= dar;
@@ -729,12 +725,12 @@ string RareItemSet::serialize_html(
}
}
string hex = example_item.short_hex();
string desc = name_index->describe_item(example_item, ItemNameIndex::Flag::NAME_ONLY);
std::string hex = example_item.short_hex();
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));
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 / {:.02f}", denom);
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) {
add_location_header(zone_type.name);
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++) {
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) {
const auto& floor_def = FloorDefinition::get(episode, floor);
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++) {
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, "");
}
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();
for (const auto& mode : ALL_GAME_MODES_V4) {
auto episodes_dict = phosg::JSON::dict();
@@ -853,12 +849,12 @@ void RareItemSet::multiply_all_rates(double factor) {
for (auto& [_, collection] : this->collections) {
for (auto& [_, specs] : collection.enemy_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& 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,
Difficulty difficulty,
uint8_t section_id,
shared_ptr<const ItemNameIndex> name_index) const {
std::shared_ptr<const ItemNameIndex> name_index) const {
const SpecCollection* collection;
try {
collection = &this->get_collection(mode, episode, difficulty, section_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
return;
}
@@ -889,18 +885,17 @@ void RareItemSet::print_collection(
try {
const auto& def = type_definition_for_enemy(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, s);
phosg::fwrite_fmt(stream, " {:<23} {}\n", def.enum_name, (name_index ? spec.str(name_index) : spec.str()));
}
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
}
phosg::fwrite_fmt(stream, " Box rares:\n");
for (size_t area_norm = 0; area_norm < collection->box_specs.size(); 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", area_norm, s);
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n",
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++) {
try {
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;
try {
this_specs = &this->enemy_specs.at(enemy_type);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
other_specs = &other.enemy_specs.at(enemy_type);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (*this_specs != *other_specs) {
phosg::fwrite_fmt(stream, " {}: {} -> {}\n",
@@ -969,11 +964,11 @@ void RareItemSet::print_diff(FILE* stream, const RareItemSet& other) const {
const SpecCollection* other_coll = nullptr;
try {
this_coll = &this->get_collection(mode, episode, difficulty, section_id);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
try {
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) {
@@ -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 {
try {
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;
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 {
try {
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;
return empty_vector;
}
@@ -1043,7 +1038,7 @@ const RareItemSet::SpecCollection& RareItemSet::get_collection(
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const {
try {
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) {
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) {
if (static_cast<size_t>(difficulty) > 3) {
throw logic_error("incorrect difficulty");
throw std::logic_error("incorrect difficulty");
}
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);
@@ -1073,7 +1068,7 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty
key |= 0x00C0;
break;
default:
throw logic_error("invalid episode in RareItemSet");
throw std::logic_error("invalid episode in RareItemSet");
}
switch (episode) {
case Episode::EP1:
@@ -1085,7 +1080,7 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty
key |= 0x0200;
break;
default:
throw logic_error("invalid episode in RareItemSet");
throw std::logic_error("invalid episode in RareItemSet");
}
return key;
}
+511 -518
View File
File diff suppressed because it is too large Load Diff
+757 -794
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -65,7 +65,7 @@ public:
uint32_t player_flags = 0;
bool player_flags_is_v3 = false;
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
PlayerVisualConfig visual;
PlayerVisualConfigSharedT<false> visual_sh;
std::string name;
PlayerStats stats;
uint32_t num_items = 0;
+77 -85
View File
@@ -8,10 +8,8 @@
#include "Loggers.hh"
#include "Server.hh"
using namespace std;
static string encode_chat_message(Version version, const string& message) {
string encoded_message;
static std::string encode_chat_message(Version version, const std::string& message) {
std::string encoded_message;
encoded_message.resize(8, 0);
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));
@@ -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)
: type(type),
client_id(client_id),
allow_size_disparity(false),
complete(false),
line_num(line_num) {}
: type(type), client_id(client_id), allow_size_disparity(false), complete(false), line_num(line_num) {}
string ReplaySession::Event::str() const {
string ret;
std::string ReplaySession::Event::str() const {
std::string ret;
if (this->type == Type::CONNECT) {
ret = std::format("Event[{}, CONNECT", this->client_id);
} else if (this->type == Type::DISCONNECT) {
@@ -46,11 +40,11 @@ string ReplaySession::Event::str() const {
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),
port(port),
version(version),
channel(make_shared<PeerChannel>(
channel(std::make_shared<PeerChannel>(
io_context,
this->version,
Language::ENGLISH,
@@ -60,12 +54,12 @@ ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t
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));
}
shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, shared_ptr<Client> c, size_t line_num) {
auto event = make_shared<Event>(type, c->id, line_num);
std::shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num) {
auto event = std::make_shared<Event>(type, c->id, line_num);
if (!this->last_event.get()) {
this->first_event = event;
} else {
@@ -78,7 +72,7 @@ shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, s
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;
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.random_seed = 0;
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 += sizeof(S_JoinGame_Ep3_64::Ep3PlayerEntry)) {
*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 0x68: {
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 += sizeof(typename CmdT::Entry)) {
*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);
mask.random_seed = 0;
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++) {
mask.spectator_players[z].disp.visual.name_color_checksum = 0;
mask.spectator_players[z].disp.visual.sh.name_color_checksum = 0;
}
}
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);
if (header.subcommand == 0x60) {
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) {
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
if (header.subcommand == 0x67) {
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)) {
const auto& header = check_size_t<G_UnusedHeader>(cmd_data, cmd_size, 0xFFFF);
if (header.subcommand == 0x70) {
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;
@@ -325,11 +319,11 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
break;
}
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),
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
commands_sent(0),
@@ -338,13 +332,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
bytes_received(0),
idle_timeout_timer(*this->state->io_context),
run_failed(false) {
shared_ptr<Event> parsing_command = nullptr;
std::shared_ptr<Event> parsing_command = nullptr;
size_t line_num = 0;
size_t num_events = 0;
while (!feof(input_log)) {
line_num++;
string line = phosg::fgets(input_log);
std::string line = phosg::fgets(input_log);
if (line.ends_with("\n")) {
line.resize(line.size() - 1);
}
@@ -353,11 +347,11 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
}
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)) {
// Parse out the hex part of the hex/ASCII dump
string mask_bytes;
string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
std::string 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->mask += mask_bytes;
continue;
@@ -378,35 +372,35 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
if (line.starts_with("### cc ")) {
// ### cc $<chat command>
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",
line_num));
}
shared_ptr<Event> event;
std::shared_ptr<Event> event;
try {
auto c = this->clients.begin()->second;
event = this->create_event(Event::Type::SEND, c, line_num);
event->data = encode_chat_message(c->version, line.substr(7));
num_events++;
} catch (const exception& e) {
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
} catch (const std::exception& e) {
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
}
continue;
} else if (line.starts_with("### on C-")) {
// ### on C-{} cc <chat command>
shared_ptr<Event> event;
std::shared_ptr<Event> event;
try {
size_t end_offset;
auto c = this->clients.at(stoull(line.substr(9), &end_offset, 16));
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->data = encode_chat_message(c->version, line.substr(end_offset + 13));
num_events++;
} catch (const exception& e) {
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
} catch (const std::exception& e) {
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
}
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-3 via TSI-9000-GC_V3-game_server
size_t offset = line.find(" - [GameServer] Client connected: C-");
if (offset != string::npos) {
if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' ');
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);
auto listen_tokens = phosg::split(tokens[10], '-');
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));
}
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
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) {
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);
num_events++;
@@ -441,24 +435,24 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
// I <pid/ts> - [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, ' ');
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-")) {
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);
try {
auto& c = this->clients.at(client_id);
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);
num_events++;
} catch (const out_of_range&) {
throw runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
}
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] Received from C-{:X} (...)
offset = line.find(" - [Commands] Sending to C-");
if (offset == string::npos) {
if (offset == std::string::npos) {
offset = line.find(" - [Commands] Received from C-");
}
if (offset != string::npos) {
if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' ');
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");
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(
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
num_events++;
} catch (const out_of_range&) {
throw runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
}
continue;
}
@@ -490,14 +484,12 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
replay_log.debug_f("{} clients in log", this->clients.size());
for (const auto& it : this->clients) {
string client_str = it.second->str();
replay_log.debug_f(" {} => {}", it.first, client_str);
replay_log.debug_f(" {} => {}", it.first, it.second->str());
}
replay_log.debug_f("{} events in replay log", num_events);
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) {
case Event::Type::CONNECT: {
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));
}
shared_ptr<const PortConfiguration> port_config;
std::shared_ptr<const PortConfiguration> port_config;
try {
port_config = this->state->number_to_port_config.at(c->port);
} catch (const out_of_range&) {
throw runtime_error(std::format(
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format(
"(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);
if (this->state->game_server.get()) {
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
} else {
throw runtime_error(std::format(
throw std::runtime_error(std::format(
"(ev-line {}) no server available for connection", this->first_event->line_num));
}
break;
@@ -542,7 +534,7 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::SEND:
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));
}
c->channel->send(this->first_event->data);
@@ -552,25 +544,25 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::RECEIVE: {
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));
}
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();
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)
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);
this->commands_received++;
this->bytes_received += full_command.size();
if (c->receive_events.empty()) {
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();
@@ -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);
replay_log.error_f("Received command:");
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])) {
replay_log.error_f("Expected command:");
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:");
phosg::print_data(stderr, full_command, 0, ev->data, 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));
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
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:
if (msg.command == 0x02) {
auto& cmd = msg.check_size_t<S_ServerInit_Patch_02>();
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
}
break;
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) {
auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
if (is_v1_or_v2(c->version)) {
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
} else { // V3
c->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
c->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
c->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
c->channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
}
}
break;
@@ -631,19 +623,19 @@ asio::awaitable<void> ReplaySession::run() {
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
// 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());
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());
}
break;
default:
throw logic_error("unsupported encryption version");
throw std::logic_error("unsupported encryption version");
}
break;
}
default:
throw logic_error("unhandled event type");
throw std::logic_error("unhandled event type");
}
this->first_event->complete = true;
}
@@ -653,7 +645,7 @@ asio::awaitable<void> ReplaySession::run() {
this->last_event = nullptr;
}
}
} catch (const exception& e) {
} catch (const std::exception& e) {
replay_log.error_f("Replay failed: {}", e.what());
if (this->first_event) {
replay_log.error_f("Next pending event: {}", this->first_event->str());
+104 -157
View File
@@ -7,13 +7,11 @@
#include "LevelTable.hh"
#include "PSOProtocol.hh"
using namespace std;
struct DefaultSymbolChatEntry {
array<const char*, 8> language_to_name;
std::array<const char*, 8> language_to_name;
uint32_t spec;
array<uint16_t, 4> corner_objects;
array<SymbolChatFacePart, 12> face_parts;
std::array<uint16_t, 4> corner_objects;
std::array<SymbolChatFacePart, 12> face_parts;
SaveFileSymbolChatEntryBB to_entry(Language language) const {
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\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}}},
@@ -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}}},
};
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,
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, 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,
@@ -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, 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, 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,
@@ -88,7 +86,7 @@ ShuffleTables::ShuffleTables(PSOV2Encryption& crypt) {
while (r28 >= 0) {
uint32_t r3 = this->pseudorand(crypt, r28 + 1);
if (r3 >= 0x100) {
throw logic_error("bad r3");
throw std::logic_error("bad r3");
}
uint8_t t = this->forward_table[r3];
this->forward_table[r3] = *r31;
@@ -144,7 +142,7 @@ bool PSOVMSFileHeader::checksum_correct() const {
void PSOVMSFileHeader::check() const {
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 {
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') {
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')) {
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') {
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')) {
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 height = this->height ? this->height.load() : 192;
if (width != 256) {
throw runtime_error("width is incorrect");
throw std::runtime_error("width is incorrect");
}
if (height != 192) {
throw runtime_error("height is incorrect");
throw std::runtime_error("height is incorrect");
}
// 4x4 blocks of pixels
@@ -307,7 +305,7 @@ bool PSOXBFileHeader::checksum_correct() const {
void PSOXBFileHeader::check() const {
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() {
{
unordered_set<uint32_t> seen;
std::unordered_set<uint32_t> seen;
size_t read_index = 0, write_index = 0;
for (read_index = 0; read_index < this->blocked_senders.size(); 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;
for (read_index = 0; read_index < this->entries.size(); read_index++) {
const auto& read_entry = this->entries[read_index];
@@ -367,23 +365,21 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() {
}
}
PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const {
PlayerDispDataBBPreview pre;
PlayerDispDataV4Preview PSOBBCharacterFile::to_preview() const {
PlayerDispDataV4Preview pre;
pre.level = this->disp.stats.level;
pre.exp = this->disp.stats.exp;
pre.visual = this->disp.visual;
pre.name = this->disp.name;
pre.play_time_seconds = this->play_time_seconds;
return pre;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
uint32_t guild_card_number,
Language language,
const PlayerVisualConfig& visual,
const std::string& name,
shared_ptr<const LevelTable> level_table) {
static const array<array<PlayerInventoryItem, 5>, 12> initial_inventory{{
const PlayerVisualConfigV4& visual,
std::shared_ptr<const LevelTable> level_table) {
static const std::array<std::array<PlayerInventoryItem, 5>, 12> initial_inventory{{
{
PlayerInventoryItem(ItemData(0x0001000000000000, 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,
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,
@@ -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}};
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,
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,
@@ -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}};
auto ret = make_shared<PSOBBCharacterFile>();
auto ret = std::make_shared<PSOBBCharacterFile>();
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();
for (size_t z = 0; z < initial_items.size(); z++) {
ret->inventory.items[z] = initial_items[z];
}
// 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},
{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},
@@ -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, 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;
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 {
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[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++) {
ret->disp.config[z] = config[z];
}
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);
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->inventory.language = language;
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.language = ret->inventory.language;
ret->guild_card.section_id = ret->disp.visual.section_id;
ret->guild_card.char_class = ret->disp.visual.char_class;
ret->guild_card.section_id = ret->disp.visual.sh.section_id;
ret->guild_card.char_class = ret->disp.visual.sh.char_class;
for (size_t z = 0; z < DEFAULT_SYMBOL_CHATS.size(); z++) {
ret->symbol_chats[z] = DEFAULT_SYMBOL_CHATS[z].to_entry(language);
}
@@ -567,26 +562,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
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) {
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
Language::JAPANESE,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
src.guild_card.guild_card_number, Language::JAPANESE, src.disp.visual);
ret->inventory = src.inventory;
ret->inventory.decode_from_client(Version::DC_V1);
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(Language::JAPANESE, Language::JAPANESE);
ret->validation_flags = 0;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -609,17 +590,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCN
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
ret->inventory.decode_from_client(Version::DC_V1);
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = 0;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -652,17 +628,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC1
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
ret->inventory.decode_from_client(Version::DC_V1);
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = src.validation_flags;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -685,17 +656,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
ret->inventory.decode_from_client(Version::DC_V2);
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = src.validation_flags;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -725,19 +691,14 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
// 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
// do this, so the data2 fields are already in the correct order here.
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = src.validation_flags;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -765,19 +726,14 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCN
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
// 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
// do this, so the data2 fields are already in the correct order here.
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = src.validation_flags;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -815,16 +771,11 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCC
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = src.validation_flags;
ret->creation_timestamp = src.creation_timestamp;
ret->play_time_seconds = src.play_time_seconds;
@@ -863,17 +814,12 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCE
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
auto ret = PSOBBCharacterFile::create_from_config(
src.guild_card.guild_card_number,
src.inventory.language,
src.disp.visual,
src.disp.visual.name.decode(Language::JAPANESE),
nullptr);
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
Language language = src.inventory.language;
auto ret = PSOBBCharacterFile::create_from_config(src.guild_card.guild_card_number, language, src.disp.visual);
ret->inventory = src.inventory;
ret->inventory.decode_from_client(Version::XB_V3);
Language language = ret->inventory.language;
ret->disp = src.disp.to_bb(language, language);
ret->disp = src.disp.to_v4(language, language);
ret->validation_flags = src.validation_flags;
ret->creation_timestamp = src.creation_timestamp;
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
// stats in the v2 format
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.masked_creation_timestamp = this->creation_timestamp ^ static_cast<uint32_t>(hardware_id >> 16);
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
// stats in the v2 format
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.masked_creation_timestamp = this->creation_timestamp ^ static_cast<uint32_t>(hardware_id >> 16);
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
// stats in the v2 format
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.validation_flags = this->validation_flags;
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
// stats in the v2 format
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.validation_flags = this->validation_flags;
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
// 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.
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.validation_flags = this->validation_flags;
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
// 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.
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.validation_flags = this->validation_flags;
ret.creation_timestamp = this->creation_timestamp;
@@ -1138,7 +1084,7 @@ PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const {
PSOXBCharacterFile::Character ret;
ret.inventory = this->inventory;
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.validation_flags = this->validation_flags;
ret.creation_timestamp = this->creation_timestamp;
@@ -1178,24 +1124,24 @@ PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const {
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 header = phosg::freadx<PSOCommandHeaderBB>(f.get());
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) {
throw runtime_error("incorrect command in character file header");
throw std::runtime_error("incorrect command in character file header");
}
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");
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) {
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;
}
@@ -1245,7 +1191,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLim
if (y < this->inventory.num_items) {
size_t new_stack_size = this->inventory.items[y].data.data1[5] + item.data1[5];
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;
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 (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];
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 && (inventory_item.data.stack_size(limits) > 1) && (amount < inventory_item.data.data1[5])) {
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.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) {
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) {
@@ -1327,7 +1273,7 @@ void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
} else if (allow_overdraft) {
this->disp.stats.meseta = 0;
} 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:
return this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2;
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;
break;
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;
level_table->reset_to_base(level_base_stats, this->disp.visual.char_class);
level_table->advance_to_level(level_base_stats, this->disp.stats.level, 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.sh.char_class);
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;
@@ -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) {
uint32_t level = this->disp.stats.level;
uint32_t exp = this->disp.stats.exp;
level_table->reset_to_base(this->disp.stats, this->disp.visual.char_class);
level_table->advance_to_level(this->disp.stats, level, 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.sh.char_class);
if (!reset_exp) {
this->disp.stats.exp = exp;
}
@@ -1490,29 +1436,30 @@ static uint16_t crc16(const void* data, size_t size) {
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) {
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) {
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")) {
throw runtime_error("user_id must end with \"@HG\"");
throw std::runtime_error("user_id must end with \"@HG\"");
}
if (token.empty()) {
throw runtime_error("token must not be empty");
throw std::runtime_error("token must not be empty");
}
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) {
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) {
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:
@@ -1526,7 +1473,7 @@ string encode_psobb_hangame_credentials(const string& user_id, const string& tok
// uint8_t unused_size;
// char unused[unused_size]; // Ignored (possibly email address?)
// 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 += user_id;
data.push_back(token.size());
+38 -33
View File
@@ -334,8 +334,8 @@ struct PSOBBMinimalSystemFile {
} __packed_ws__(PSOBBMinimalSystemFile, 0x114);
struct PSOBBBaseSystemFile : PSOBBMinimalSystemFile {
/* 0114 */ parray<uint8_t, 0x016C> key_config;
/* 0280 */ parray<uint8_t, 0x0038> joystick_config;
/* 0114 */ parray<uint8_t, 0x16C> key_config;
/* 0280 */ parray<uint8_t, 0x38> joystick_config;
/* 02B8 */
PSOBBBaseSystemFile();
@@ -349,7 +349,7 @@ struct PSODCNTECharacterFile {
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 034C:---- */ PlayerDispDataV123 disp;
// 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
// top and bottom 16 bits are ignored for this purpose).
@@ -385,7 +385,7 @@ struct PSODC112000CharacterFile {
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 034C:---- */ PlayerDispDataV123 disp;
/* 041C:0000 */ le_uint32_t masked_creation_timestamp = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xBB40711D;
@@ -417,7 +417,7 @@ struct PSODCV1CharacterFile {
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 034C:---- */ PlayerDispDataV123 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
@@ -445,7 +445,7 @@ struct PSODCV2CharacterFile {
struct Character {
// See PSOGCCharacterFile::Character for descriptions of fields' meanings.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 034C:---- */ PlayerDispDataV123 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xA205B064;
@@ -491,7 +491,7 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
/* 0000 */ le_uint32_t checksum = 0;
struct Character {
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataDCPCV3 disp;
/* 034C */ PlayerDispDataV123 disp;
/* 041C */ be_uint32_t validation_flags = 0;
/* 0420 */ be_uint32_t creation_timestamp = 0;
/* 0424 */ be_uint32_t signature = 0x6C5D889E;
@@ -528,7 +528,7 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD
struct PSOGCNTECharacterFileCharacter {
/* 0000:---- */ PlayerInventoryBE inventory;
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
/* 034C:---- */ PlayerDispDataV123BE disp;
/* 041C:0000 */ be_uint32_t validation_flags = 0;
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
/* 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
// structure (first column), and relative to the start of the second internal structure (second column).
/* 0000:---- */ PlayerInventoryBE inventory;
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
/* 034C:---- */ PlayerDispDataV123BE disp;
// Known bits in the validation_flags field:
// 00000001: Character was not saved after disconnecting (and the message about items being deleted is shown in
// 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
// structure (first column), and relative to the start of the second internal structure (second column).
/* 0000:---- */ PlayerInventoryBE inventory;
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
/* 034C:---- */ PlayerDispDataV123BE disp;
/* 041C:0000 */ be_uint32_t validation_flags = 0;
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
/* 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
// structure (first column), and relative to the start of the second internal structure (second column).
/* 0000:---- */ PlayerInventoryBE inventory;
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
/* 034C:---- */ PlayerDispDataV123BE disp;
/* 041C:0000 */ be_uint32_t validation_flags = 0;
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
/* 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
// fields have the same meanings as in PSOGCCharacterFile::Character.
/* 0000:---- */ PlayerInventory inventory;
/* 034C:---- */ PlayerDispDataDCPCV3 disp;
/* 034C:---- */ PlayerDispDataV123 disp;
/* 041C:0000 */ le_uint32_t validation_flags = 0;
/* 0420:0004 */ le_uint32_t creation_timestamp = 0;
/* 0424:0008 */ le_uint32_t signature = 0xC87ED5B1;
@@ -794,7 +794,7 @@ struct PSOBBCharacterFile {
// .psochar file. See PSOCHARFile below for the full file format.
/* 0000 */ PlayerInventory inventory;
/* 034C */ PlayerDispDataBB disp;
/* 034C */ PlayerDispDataV4 disp;
/* 04DC */ le_uint32_t validation_flags = 0;
/* 04E0 */ le_uint32_t creation_timestamp = 0;
/* 04E4 */ le_uint32_t signature = 0xC87ED5B1;
@@ -808,34 +808,39 @@ struct PSOBBCharacterFile {
/* 1AC8 */ le_uint32_t unknown_a3 = 0;
/* 1ACC */ parray<SaveFileSymbolChatEntryBB, 0x0C> symbol_chats;
/* 1FAC */ parray<SaveFileShortcutEntryBB, 0x10> shortcuts;
/* 29EC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 2B44 */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
/* 29EC */ pstring<TextEncoding::UTF16, 0xAC> auto_reply;
/* 2B44 */ pstring<TextEncoding::UTF16, 0xAC> info_board;
/* 2C9C */ PlayerRecordsBattle battle_records;
/* 2CB4 */ parray<uint8_t, 4> unknown_a4;
/* 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;
/* 2E38 */ parray<uint8_t, 0x0010> unknown_a6;
/* 2E48 */ parray<le_uint32_t, 0x0010> quest_counters;
/* 2E38 */ parray<uint8_t, 0x10> unknown_a6;
/* 2E48 */ parray<le_uint32_t, 0x10> quest_counters;
/* 2E88 */ PlayerRecordsBattle offline_battle_records;
/* 2EA0 */ parray<uint8_t, 4> unknown_a7;
/* 2EA4 */
PSOBBCharacterFile() = default;
PlayerDispDataBBPreview to_preview() const;
PlayerDispDataV4Preview to_preview() const;
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
uint32_t guild_card_number,
Language language,
const PlayerVisualConfig& visual,
const std::string& name,
std::shared_ptr<const LevelTable> level_table);
static std::shared_ptr<PSOBBCharacterFile> create_from_preview(
const PlayerVisualConfigV4& visual,
std::shared_ptr<const LevelTable> level_table = nullptr);
template <bool BE>
static std::shared_ptr<PSOBBCharacterFile> create_from_config(
uint32_t guild_card_number,
Language language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
const PlayerVisualConfigV123T<BE>& visual,
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 PSODC112000CharacterFile::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;
/* 0008 */ parray<uint8_t, 0x20> unused;
/* 0028 */ PlayerRecordsBattle battle_records;
/* 0040 */ PlayerDispDataBBPreview preview;
/* 00BC */ pstring<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 0040 */ PlayerDispDataV4Preview preview;
/* 00BC */ pstring<TextEncoding::UTF16, 0xAC> auto_reply;
/* 0214 */ PlayerBank200 bank;
/* 14DC */ PlayerRecordsChallengeBB challenge_records;
/* 161C */ PlayerDispDataBB disp;
/* 17AC */ pstring<TextEncoding::UTF16, 0x0058> guild_card_description;
/* 185C */ pstring<TextEncoding::UTF16, 0x00AC> info_board;
/* 161C */ PlayerDispDataV4 disp;
/* 17AC */ pstring<TextEncoding::UTF16, 0x58> guild_card_description;
/* 185C */ pstring<TextEncoding::UTF16, 0xAC> info_board;
/* 19B4 */ PlayerInventory inventory;
/* 1D00 */ parray<uint8_t, 4> unknown_a2;
/* 1D04 */ QuestFlags quest_flags;
/* 1F04 */ le_uint32_t death_count = 0;
/* 1F08 */ parray<le_uint32_t, 0x0016> quest_counters;
/* 1F60 */ parray<le_uint16_t, 0x0014> tech_menu_shortcut_entries;
/* 1F08 */ parray<le_uint32_t, 0x16> quest_counters;
/* 1F60 */ parray<le_uint16_t, 0x14> tech_menu_shortcut_entries;
/* 1F88 */
} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88);
@@ -1054,7 +1059,7 @@ struct LegacySavedAccountDataBB { // .nsa file format
static const char* 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;
/* D648 */ PSOBBBaseSystemFile system_file;
/* D880 */ PSOBBFullTeamMembership team_membership;
+455 -444
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -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;
// 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.
// 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_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_guild_card_header_bb(std::shared_ptr<Client> c);
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
+4 -6
View File
@@ -13,9 +13,7 @@
#include "ShellCommands.hh"
#include "StaticGameData.hh"
using namespace std;
ServerShell::ServerShell(shared_ptr<ServerState> state)
ServerShell::ServerShell(std::shared_ptr<ServerState> state)
: state(state), th(&ServerShell::thread_fn, this) {}
ServerShell::~ServerShell() {
@@ -28,7 +26,7 @@ void ServerShell::thread_fn() {
for (;;) {
phosg::fwrite_fmt(stdout, "newserv> ");
fflush(stdout);
string command;
std::string command;
uint64_t read_start_usecs = phosg::now();
try {
command = phosg::fgets(stdin);
@@ -53,7 +51,7 @@ void ServerShell::thread_fn() {
phosg::strip_leading_whitespace(command);
try {
std::promise<deque<string>> promise;
std::promise<std::deque<std::string>> promise;
auto future = promise.get_future();
asio::co_spawn(
@@ -73,7 +71,7 @@ void ServerShell::thread_fn() {
} catch (const exit_shell&) {
this->state->io_context->stop();
return;
} catch (const exception& e) {
} catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
}
}
+378 -397
View File
File diff suppressed because it is too large Load Diff
+4 -11
View File
@@ -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::shared_ptr<const RoomLayoutIndex> room_layout_index;
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 Episode3::CardIndex> ep3_card_index;
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::shared_ptr<AccountIndex> account_index;
bool allow_saving_accounts = true;
std::shared_ptr<IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<TeamIndex> team_index;
phosg::JSON team_reward_defs_json;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<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_v2;
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_pc;
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);
std::shared_ptr<const std::string> load_bb_file(
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_bb_file(const std::string& patch_index_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;
@@ -452,7 +446,6 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
void load_teams();
void load_patch_indexes();
void load_maps();
void clear_file_caches();
void load_battle_params();
void load_level_tables();
void load_text_index();
+169 -165
View File
@@ -14,48 +14,48 @@
#include "ServerState.hh"
#include "StaticGameData.hh"
using namespace std;
vector<const ShellCommand*> ShellCommand::commands_by_order;
unordered_map<string, const ShellCommand*> ShellCommand::commands_by_name;
std::vector<const ShellCommand*> ShellCommand::commands_by_order;
std::unordered_map<std::string, const ShellCommand*> ShellCommand::commands_by_name;
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) {
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()) {
return this->s->game_server->get_client();
} else {
auto clients = this->s->game_server->get_clients_by_identifier(this->session_name);
if (clients.empty()) {
throw runtime_error("no such client");
throw std::runtime_error("no such client");
}
if (clients.size() > 1) {
throw runtime_error("multiple clients found");
throw std::runtime_error("multiple clients found");
}
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();
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;
}
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) {
ShellCommand::commands_by_order.emplace_back(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 args_begin = phosg::skip_whitespace(command, command_end);
Args args;
@@ -65,21 +65,21 @@ asio::awaitable<deque<string>> ShellCommand::dispatch_str(shared_ptr<ServerState
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;
try {
def = commands_by_name.at(args.command);
} catch (const out_of_range&) {
} catch (const std::out_of_range&) {
}
if (!def) {
throw runtime_error("no such command; try 'help'");
throw std::runtime_error("no such command; try 'help'");
} else {
return def->run(args);
}
}
static string get_quoted_string(string& s) {
string ret;
static std::string get_quoted_string(std::string& s) {
std::string ret;
char end_char = (s.at(0) == '\"') ? '\"' : ' ';
size_t z = (s.at(0) == '\"') ? 1 : 0;
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()) {
ret.push_back(s[z + 1]);
} else {
throw runtime_error("incomplete escape sequence");
throw std::runtime_error("incomplete escape sequence");
}
} else {
ret.push_back(s[z]);
@@ -95,7 +95,7 @@ static string get_quoted_string(string& s) {
}
if (end_char != ' ') {
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));
} else {
@@ -104,8 +104,8 @@ static string get_quoted_string(string& s) {
return ret;
}
static asio::awaitable<deque<string>> empty_handler(ShellCommand::Args&) {
co_return deque<string>();
static asio::awaitable<std::deque<std::string>> empty_handler(ShellCommand::Args&) {
co_return std::deque<std::string>();
}
ShellCommand c_nop1("", nullptr, empty_handler);
@@ -115,8 +115,8 @@ ShellCommand c_nop3("#", nullptr, empty_handler);
ShellCommand c_help(
"help", "help\n\
You\'re reading it now.",
+[](ShellCommand::Args&) -> asio::awaitable<deque<string>> {
deque<string> ret({"Commands:"});
+[](ShellCommand::Args&) -> asio::awaitable<std::deque<std::string>> {
std::deque<std::string> ret({"Commands:"});
for (const auto& def : ShellCommand::commands_by_order) {
if (def->help_text) {
// TODO: It's not great that we copy the text here.
@@ -129,7 +129,7 @@ ShellCommand c_help(
ShellCommand c_exit(
"exit", "exit (or ctrl+d)\n\
Shut down the server.",
+[](ShellCommand::Args&) -> asio::awaitable<deque<string>> {
+[](ShellCommand::Args&) -> asio::awaitable<std::deque<std::string>> {
throw exit_shell();
});
ShellCommand c_on(
@@ -141,7 +141,7 @@ ShellCommand c_on(
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\
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 command_begin = phosg::skip_whitespace(args.args, session_name_end);
size_t command_end = phosg::skip_non_whitespace(args.args, command_begin);
@@ -158,7 +158,6 @@ ShellCommand c_reload(
accounts - reindex user accounts\n\
battle-params - reload the BB enemy stats files\n\
bb-keys - reload BB private keys\n\
caches - clear all cached files\n\
config - reload most fields from config.json\n\
dol-files - reindex all DOL files\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\
restarting, clients may see (for example) EXP messages inconsistent with\n\
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, ' ');
for (const auto& type : types) {
if (type == "all") {
@@ -193,8 +192,6 @@ ShellCommand c_reload(
args.s->load_accounts();
} else if (type == "maps") {
args.s->load_maps();
} else if (type == "caches") {
args.s->clear_file_caches();
} else if (type == "patch-files") {
args.s->load_patch_indexes();
} else if (type == "ep3-cards") {
@@ -234,18 +231,18 @@ ShellCommand c_reload(
} else if (type == "quests") {
args.s->load_quest_index();
} 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(
"list-accounts", "list-accounts\n\
List all accounts registered on the server.",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
deque<string> ret;
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::deque<std::string> ret;
auto accounts = args.s->account_index->all();
if (accounts.empty()) {
ret.emplace_back("No accounts registered");
@@ -257,20 +254,20 @@ ShellCommand c_list_accounts(
co_return ret;
});
uint32_t parse_account_flags(const string& flags_str) {
uint32_t parse_account_flags(const std::string& flags_str) {
try {
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()) {
return ret;
}
} catch (const exception&) {
} catch (const std::exception&) {
}
uint32_t ret = 0;
auto tokens = phosg::split(flags_str, ',');
for (const auto& token : tokens) {
string token_upper = phosg::toupper(token);
std::string token_upper = phosg::toupper(token);
if (token_upper == "NONE") {
// Nothing to do
} 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") {
ret |= static_cast<uint32_t>(Account::Flag::IS_SHARED_ACCOUNT);
} else {
throw runtime_error("invalid flag name: " + token_upper);
throw std::runtime_error("invalid flag name: " + token_upper);
}
}
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 {
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()) {
return ret;
}
} catch (const exception&) {
} catch (const std::exception&) {
}
uint32_t ret = 0;
auto tokens = phosg::split(user_flags_str, ',');
for (const auto& token : tokens) {
string token_upper = phosg::toupper(token);
std::string token_upper = phosg::toupper(token);
if (token_upper == "NONE") {
// Nothing to do
} else if (token_upper == "DISABLE_DROP_NOTIFICATION_BROADCAST") {
ret |= static_cast<uint32_t>(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST);
} else {
throw runtime_error("invalid user flag name: " + token_upper);
throw std::runtime_error("invalid user flag name: " + token_upper);
}
}
return ret;
@@ -363,15 +360,15 @@ ShellCommand c_add_account(
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\
names)",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
auto account = make_shared<Account>();
for (const string& token : phosg::split(args.args, ' ')) {
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
auto account = std::make_shared<Account>();
for (const std::string& token : phosg::split(args.args, ' ')) {
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=")) {
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=")) {
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") {
account->is_temporary = true;
} else if (token.starts_with("flags=")) {
@@ -379,12 +376,12 @@ ShellCommand c_add_account(
} else if (token.starts_with("user-flags=")) {
account->user_flags = parse_account_user_flags(token.substr(11));
} else {
throw invalid_argument("invalid account field: " + token);
throw std::invalid_argument("invalid account field: " + token);
}
}
args.s->account_index->add(account);
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(
"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\
therefore will be deleted when the server shuts down.\n\
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, ' ');
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));
tokens.erase(tokens.begin());
@@ -419,11 +416,11 @@ ShellCommand c_update_account(
int64_t new_user_flags = -1;
uint8_t new_is_temporary = 0xFF;
int64_t new_ban_duration = -1;
for (const string& token : tokens) {
for (const std::string& token : tokens) {
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=")) {
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") {
new_is_temporary = 1;
} else if (token == "permanent") {
@@ -451,10 +448,10 @@ ShellCommand c_update_account(
} else if (duration_str.ends_with("y")) {
new_ban_duration = stoull(duration_str.substr(0, duration_str.size() - 1)) * 31536000000000LL;
} else {
throw runtime_error("invalid time unit");
throw std::runtime_error("invalid time unit");
}
} 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();
}
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(
"delete-account", "delete-account ACCOUNT-ID\n\
Delete an account from the server. If a player is online with the deleted\n\
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));
args.s->account_index->remove(account->account_id);
account->is_temporary = true;
account->delete_file();
co_return deque<string>{"Account deleted"};
co_return std::deque<std::string>{"Account deleted"};
});
ShellCommand c_add_license(
@@ -512,57 +509,57 @@ ShellCommand c_add_license(
add-license 385A92C4 DC 107862F9 d38XTu2p\n\
add-license 385A92C4 GC 0418572923 282949185033 hunter2\n\
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, ' ');
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));
string type_str = phosg::toupper(tokens[1]);
std::string type_str = phosg::toupper(tokens[1]);
if (type_str == "DC-NTE") {
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->access_key = std::move(tokens[3]);
args.s->account_index->add_dc_nte_license(account, license);
} else if (type_str == "DC") {
if (tokens.size() != 4) {
throw runtime_error("incorrect number of parameters");
throw std::runtime_error("incorrect number of parameters");
}
auto license = make_shared<V1V2License>();
license->serial_number = stoul(tokens[2], nullptr, 16);
auto license = std::make_shared<V1V2License>();
license->serial_number = std::stoul(tokens[2], nullptr, 16);
license->access_key = std::move(tokens[3]);
args.s->account_index->add_dc_license(account, license);
} else if (type_str == "PC") {
if (tokens.size() != 4) {
throw runtime_error("incorrect number of parameters");
throw std::runtime_error("incorrect number of parameters");
}
auto license = make_shared<V1V2License>();
license->serial_number = stoul(tokens[2], nullptr, 16);
auto license = std::make_shared<V1V2License>();
license->serial_number = std::stoul(tokens[2], nullptr, 16);
license->access_key = std::move(tokens[3]);
args.s->account_index->add_pc_license(account, license);
} else if (type_str == "GC") {
if (tokens.size() != 5) {
throw runtime_error("incorrect number of parameters");
throw std::runtime_error("incorrect number of parameters");
}
auto license = make_shared<GCLicense>();
license->serial_number = stoul(tokens[2], nullptr, 10);
auto license = std::make_shared<GCLicense>();
license->serial_number = std::stoul(tokens[2], nullptr, 10);
license->access_key = std::move(tokens[3]);
license->password = std::move(tokens[4]);
args.s->account_index->add_gc_license(account, license);
} else if (type_str == "XB") {
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->user_id = stoull(tokens[3], nullptr, 16);
license->account_id = stoull(tokens[4], nullptr, 16);
@@ -570,19 +567,19 @@ ShellCommand c_add_license(
} else if (type_str == "BB") {
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->password = std::move(tokens[3]);
args.s->account_index->add_bb_license(account, license);
} else {
throw runtime_error("invalid license type");
throw std::runtime_error("invalid license type");
}
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(
"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 XB 7E29A2950019EB20\n\
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, ' ');
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));
string type_str = phosg::toupper(tokens[1]);
std::string type_str = phosg::toupper(tokens[1]);
if (type_str == "DC-NTE") {
args.s->account_index->remove_dc_nte_license(account, tokens[2]);
} 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") {
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") {
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") {
args.s->account_index->remove_xb_license(account, stoull(tokens[2], nullptr, 16));
} else if (type_str == "BB") {
args.s->account_index->remove_bb_license(account, tokens[2]);
} else {
throw runtime_error("invalid license type");
throw std::runtime_error("invalid license type");
}
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(
"lookup", "lookup USER\n\
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);
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)};
} else {
// 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(
@@ -648,26 +645,26 @@ ShellCommand c_kick(
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\
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);
send_message_box(target, "$C6You have been kicked off the server.");
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(
"announce", "announce MESSAGE\n\
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);
co_return deque<string>{};
co_return std::deque<std::string>{};
});
ShellCommand c_announce_mail(
"announce-mail", "announce-mail MESSAGE\n\
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);
co_return deque<string>{};
co_return std::deque<std::string>{};
});
ShellCommand c_create_tournament(
@@ -698,11 +695,11 @@ ShellCommand c_create_tournament(
dialogue=ON/OFF: Enable/disable dialogue\n\
dice-exchange=ATK/DEF/NONE: Set dice exchange mode\n\
dice-boost=ON/OFF: Enable/disable dice boost",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
string name = get_quoted_string(args.args);
string map_name = get_quoted_string(args.args);
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string 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);
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;
rules.set_defaults();
uint8_t flags = Episode3::Tournament::Flag::HAS_COM_TEAMS;
@@ -719,24 +716,24 @@ ShellCommand c_create_tournament(
} else if (token == "resize") {
flags |= Episode3::Tournament::Flag::RESIZE_ON_START;
} 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, '-');
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);
};
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, '-');
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), ':');
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]);
rules.min_dice_value = atk_range.first;
@@ -750,7 +747,7 @@ ShellCommand c_create_tournament(
} else if (subtokens.size() == 4) {
rules.def_dice_value_range_2v1 = parse_range_c(subtokens[3]);
} else {
throw runtime_error("too many range specs given");
throw std::runtime_error("too many range specs given");
}
} else {
rules.atk_dice_value_range_2v1 = 0;
@@ -762,18 +759,18 @@ ShellCommand c_create_tournament(
rules.def_dice_value_range_2v1 = 0;
}
} 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) {
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) {
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;
} 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=")) {
rules.char_hp = stoul(token.substr(3));
rules.char_hp = std::stoul(token.substr(3));
} else if (token == "allowed-cards=all") {
rules.allowed_cards = Episode3::AllowedCards::ALL;
} else if (token == "allowed-cards=n") {
@@ -815,11 +812,11 @@ ShellCommand c_create_tournament(
} else if (token == "dice-exchange=none") {
rules.dice_exchange_mode = Episode3::DiceExchangeMode::NONE;
} 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()) {
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 a tournament. Quotes are required around the tournament name unless\n\
the name contains no spaces.",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
string name = get_quoted_string(args.args);
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string name = get_quoted_string(args.args);
if (args.s->ep3_tournament_index->delete_tournament(name)) {
co_return deque<string>{"Deleted tournament"};
co_return std::deque<std::string>{"Deleted tournament"};
} else {
throw runtime_error("tournament does not exist");
throw std::runtime_error("tournament does not exist");
}
});
ShellCommand c_list_tournaments(
"list-tournaments", "list-tournaments\n\
List the names and numbers of all existing tournaments.",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
deque<string> ret;
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::deque<std::string> ret;
for (const auto& it : args.s->ep3_tournament_index->all_tournaments()) {
ret.emplace_back(" " + it.second->get_name());
}
@@ -854,30 +851,30 @@ ShellCommand c_start_tournament(
"start-tournament", "start-tournament TOURNAMENT-NAME\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.",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
string name = get_quoted_string(args.args);
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string name = get_quoted_string(args.args);
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
if (tourn) {
tourn->start();
args.s->ep3_tournament_index->save();
tourn->send_all_state_updates();
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 {
throw runtime_error("tournament does not exist");
throw std::runtime_error("tournament does not exist");
}
});
ShellCommand c_describe_tournament(
"describe-tournament", "describe-tournament TOURNAMENT-NAME\n\
Show the current state of a tournament. Quotes are required around the\n\
tournament name unless the name contains no spaces.",
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
string name = get_quoted_string(args.args);
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string name = get_quoted_string(args.args);
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
if (tourn) {
co_return deque<string>{tourn->bracket_str()};
co_return std::deque<std::string>{tourn->bracket_str()};
} 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\
require cheat mode or debug mode are always available via cc even if the\n\
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();
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) {
string data = phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
asio::awaitable<std::deque<std::string>> f_sc_ss(ShellCommand::Args& args) {
std::string data = phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
if (data.size() == 0) {
throw invalid_argument("no data given");
throw std::invalid_argument("no data given");
}
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());
}
co_return deque<string>{};
co_return std::deque<std::string>{};
}
ShellCommand c_sc("sc", "sc DATA\n\
@@ -929,10 +926,10 @@ ShellCommand c_show_slots(
"show-slots", "show-slots\n\
Show the player names, Guild Card numbers, and client IDs of all players in\n\
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();
deque<string> ret;
std::deque<std::string> ret;
for (size_t z = 0; z < c->proxy_session->lobby_players.size(); z++) {
const auto& player = c->proxy_session->lobby_players[z];
if (player.guild_card_number) {
@@ -942,13 +939,13 @@ ShellCommand c_show_slots(
name_for_char_class(player.char_class),
name_for_section_id(player.section_id)));
} else {
ret.emplace_back(format(" {}: (no player)", z));
ret.emplace_back(std::format(" {}: (no player)", z));
}
}
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();
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())) {
send_chat_message_from_client(c->proxy_session->server_channel, args.args, 0);
} else {
string data(8, '\0');
std::string data(8, '\0');
data.push_back('\x09');
data.push_back('E');
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);
}
} 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();
for (auto& lc : l->clients) {
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_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.",
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();
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) {
string data(8, '\0');
std::string data(8, '\0');
data.push_back('\x40'); // private_flags: visible to all
data.push_back('\x09');
data.push_back('E');
@@ -1007,11 +1007,15 @@ asio::awaitable<deque<string>> fn_wchat(ShellCommand::Args& args) {
for (auto& lc : l->clients) {
if (lc) {
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_wchat("wchat", "wchat TEXT\n\
@@ -1021,20 +1025,20 @@ ShellCommand c_wchat("wchat", "wchat TEXT\n\
ShellCommand c_marker(
"marker", "marker COLOR-ID\n\
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();
c->proxy_session->server_channel->send(0x89, stoul(args.args));
co_return deque<string>{};
c->proxy_session->server_channel->send(0x89, std::stoul(args.args));
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();
uint8_t floor = stoul(args.args);
uint8_t floor = std::stoul(args.args);
send_warp(c->channel, c->lobby_client_id, floor, true);
if (args.command == "warpall") {
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_warpme("warpme", "warpme FLOOR-ID\n\
@@ -1044,10 +1048,10 @@ ShellCommand c_warpall("warpall", "warpall FLOOR-ID\n\
Send everyone to a specific floor.",
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();
string data;
std::string data;
if (args.command == "info-board-data") {
data += phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
} else {
@@ -1057,7 +1061,7 @@ asio::awaitable<deque<string>> fn_info_board(ShellCommand::Args& args) {
data.resize((data.size() + 3) & (~3));
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\
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(
"create-item", "create-item DATA\n\
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();
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) {
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) {
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);
@@ -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->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);
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