Merge branch 'pr-3-salmon'
This commit is contained in:
commit
a4a8e0b6f8
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"csharpier": {
|
||||||
|
"version": "0.28.2",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-csharpier"
|
||||||
|
],
|
||||||
|
"rollForward": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"[csharp]": {
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.defaultFormatter": "csharpier.csharpier-vscode",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnPaste": true
|
||||||
|
}
|
||||||
|
}
|
121
README.md
121
README.md
|
@ -1,76 +1,69 @@
|
||||||
# SCHALE.GameServer
|
# SCHALE.GameServer
|
||||||
|
|
||||||
## Running
|
> [!TIP]
|
||||||
By default the server is configured to run with SQL Server Express in `appsettings.json`. If you wanna use other edition of SQL Server Express change the `ConnectionStrings` in there.
|
> For original README please refer to <https://github.com/rafi1212122/SCHALE.GameServer>
|
||||||
|
|
||||||
Alternatively this software can run in docker too (`docker compose up --build`).
|
## Prerequisites
|
||||||
|
|
||||||
## Connecting
|
- Some computer knowledge
|
||||||
- Run the game with this [frida script](https://gist.githubusercontent.com/raphaeIl/c4ca030411186c9417da22d8d7864c4d/raw/00b69c5bacdf79c24972411bd80d785eed3841ce/ba.js)
|
- [.NET SDK 8.0](https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0)
|
||||||
|
- [SQL Express](https://www.microsoft.com/zh-tw/sql-server/sql-server-downloads)
|
||||||
|
- [SQL Server Management Studio (SSMS)](https://learn.microsoft.com/zh-tw/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16)
|
||||||
|
- [LD Player 9](https://www.ldplayer.tw/)
|
||||||
|
- [Python](https://www.python.org/)
|
||||||
|
- [Frida](https://frida.re/)
|
||||||
|
- [frida-server-16.2.5-android-x86_64](https://github.com/frida/frida/releases)
|
||||||
|
|
||||||
## Discuss
|
## Steps
|
||||||
[Discord Server](https://discord.gg/fbsRYc7bBA)
|
|
||||||
|
|
||||||
## How to use (E.g Windows and LDPlayer 9)
|
1. Start SQL server
|
||||||
1. Download this repo.
|
2. Start private game server
|
||||||
```sh
|
3. Start LD Player
|
||||||
git clone https://github.com/rafi1212122/SCHALE.GameServer.git
|
4. Start Frida server
|
||||||
|
5. Start ブルアカ
|
||||||
|
6. Inject Frida script
|
||||||
|
7. Enjoy :smile:
|
||||||
|
|
||||||
|
### SQL server
|
||||||
|
|
||||||
|
Use SSMS to connect with default settings except that you have to check "Trust server certificate".
|
||||||
|
|
||||||
|
### Game server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# in this repo
|
||||||
|
cd SCHALE.GameServer
|
||||||
|
dotnet run
|
||||||
```
|
```
|
||||||
2. Download .NET SDK, SQL Server Express, and install them.
|
|
||||||
- .NET SDK<br>
|
### Frida server
|
||||||
<https://dotnet.microsoft.com/zh-cn/download/dotnet?cid=getdotnetcorecli>
|
|
||||||
- SQL Server Express<br>
|
1. Extract `frida-server-16.2.5-android-x86_64.xz`
|
||||||
<https://go.microsoft.com/fwlink/p/?linkid=2216019&clcid=0x804&culture=zh-cn&country=cn>
|
to `LDPlayer/frida-server-16.2.5-android-x86_64`.
|
||||||
3. Download Visual Studio and Install C# and Database Extensions.<br>
|
2. Turn on LD Player
|
||||||
[https://visualstudio.microsoft.com/zh-hans/vs/](https://visualstudio.microsoft.com/vs/)
|
3. Turn on root and adb in the settings of LD Player.
|
||||||
4. Use Visual Studio to open `SCHALE.GameServer\SCHALE.GameServer.sln` and make it.
|
4.
|
||||||
5. Download `Excel.zip` and unzip and add the excels to the following path: `SCHALE.GameServer\bin\Debug\net8.0\Resources\excel`.
|
|
||||||
6. Modify the IP address in `SCHALE.GameServer-master\SCHALE.GameServer\bin\Debug\net8.0\Config.json`.
|
```bash
|
||||||
7. Open `SCHALE.GameServer-master\SCHALE.GameServer\bin\Debug\net8.0\SCHALE.GameServer.exe`.
|
# in LDPlayer
|
||||||
8. Download python and install frida<br>You may need to add python site packages to PATH if "frida" command is missing.<br>
|
cd LDPlayer9
|
||||||
<https://www.python.org/>
|
./adb.exe push ../frida-server-16.2.5-android-x86_64 /data/local/tmp/frida-server
|
||||||
```sh
|
./adb.exe shell
|
||||||
pip install frida-tools
|
su
|
||||||
pip install frida
|
cd /data/local/tmp
|
||||||
|
chmod 755 frida-server
|
||||||
|
./frida-server
|
||||||
```
|
```
|
||||||
9. Download frida-server(if you use emulator, download x86), find "frida-server-[Latest Version]-android-x86_64.xz" and download & unzip it, before push you may rename the bin file to "frida-server".<br>
|
|
||||||
<https://github.com/frida/frida/releases>
|
|
||||||
10. Download adb.<br>
|
|
||||||
<https://developer.android.google.cn/tools/releases/platform-tools?hl=zh-cn#downloads>
|
|
||||||
11. Use adb connect emulator and start frida-server(enable root first)
|
|
||||||
```sh
|
|
||||||
adb root
|
|
||||||
adb push frida-server /data/local/tmp/
|
|
||||||
adb shell "chmod 755 /data/local/tmp/frida-server"
|
|
||||||
adb shell "/data/local/tmp/frida-server &"
|
|
||||||
```
|
|
||||||
12. Start BluearchiveJP and use frida script
|
|
||||||
1. download this [frida script](https://gist.githubusercontent.com/raphaeIl/c4ca030411186c9417da22d8d7864c4d/raw/00b69c5bacdf79c24972411bd80d785eed3841ce/ba.js) and modify `SERVER_ADDRESS`
|
|
||||||
2. Start BluearchiveJP first and then start frida
|
|
||||||
3. `frida -U "ブルアカ" -l ba.js --realm=emulated`
|
|
||||||
13. Skip Tutorial<br>
|
|
||||||
If you have never finished any tutorials try this<br>
|
|
||||||
`INSERT INTO [dbo].[AccountTutorials] ([AccountServerId], [TutorialIds]) VALUES (1, N'[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]');`<br>
|
|
||||||
or you have finished the front part of the tutorial use this<br>
|
|
||||||
`UPDATE [dbo].[AccountTutorials] SET [TutorialIds] = N'[1, 2, 3, 4, 5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]' WHERE [AccountServerId] = [Your Server ID];`
|
|
||||||
|
|
||||||
## Command
|
### Inject Frida script
|
||||||
Go to club and input `/help` to view command usage
|
|
||||||
|
|
||||||
## Troubleshooting
|
> [!NOTE]
|
||||||
|
> Edit line 5 of [ba.js](./ba.js) to your own server IP.
|
||||||
|
|
||||||
##### YostarRequestFail:INIT_FAILED(Initialization failed)
|
> [!WARNING]
|
||||||
- change `SERVER_ADDRESS` in `ba.js` to your IPV4 and not `0.0.0.0`
|
> Do this fast when you open ブルアカ and see the Yostar logo.
|
||||||
|
|
||||||
##### アカウント情報の連携中にエラーガ発生乚ま乚た
|
```bash
|
||||||
- restart server or without `Resources\excel`
|
# in this repo
|
||||||
|
frida -U "ブルアカ" -l ba.js --realm=emulated
|
||||||
##### can enter game but black screen
|
```
|
||||||
- close server and delete database, then open server.<br>
|
|
||||||
if it isn't work, you may need to pass the official tutorial first.
|
|
||||||
|
|
||||||
##### failed to load club
|
|
||||||
- change `Address` in `config.json` to your IPV4
|
|
||||||
|
|
||||||
##### command not work
|
|
||||||
- the excel you are using may have problem, change your excel.
|
|
|
@ -0,0 +1 @@
|
||||||
|
*
|
|
@ -0,0 +1 @@
|
||||||
|
*
|
|
@ -0,0 +1 @@
|
||||||
|
https://prod-clientpatch.bluearchiveyostar.com/r67_utfwo6vcvx7rhl017phc
|
|
@ -1,8 +1,8 @@
|
||||||
using SCHALE.Common.Database;
|
using SCHALE.Common.Database;
|
||||||
|
using SCHALE.Common.Database.ModelExtensions;
|
||||||
|
using SCHALE.Common.FlatData;
|
||||||
using SCHALE.Common.NetworkProtocol;
|
using SCHALE.Common.NetworkProtocol;
|
||||||
using SCHALE.GameServer.Services;
|
using SCHALE.GameServer.Services;
|
||||||
using SCHALE.Common.FlatData;
|
|
||||||
using SCHALE.Common.Database.ModelExtensions;
|
|
||||||
|
|
||||||
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,13 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
private readonly SCHALEContext context;
|
private readonly SCHALEContext context;
|
||||||
private readonly ExcelTableService excelTableService;
|
private readonly ExcelTableService excelTableService;
|
||||||
|
|
||||||
public Account(IProtocolHandlerFactory protocolHandlerFactory, ISessionKeyService _sessionKeyService, SCHALEContext _context, ExcelTableService _excelTableService) : base(protocolHandlerFactory)
|
public Account(
|
||||||
|
IProtocolHandlerFactory protocolHandlerFactory,
|
||||||
|
ISessionKeyService _sessionKeyService,
|
||||||
|
SCHALEContext _context,
|
||||||
|
ExcelTableService _excelTableService
|
||||||
|
)
|
||||||
|
: base(protocolHandlerFactory)
|
||||||
{
|
{
|
||||||
sessionKeyService = _sessionKeyService;
|
sessionKeyService = _sessionKeyService;
|
||||||
context = _context;
|
context = _context;
|
||||||
|
@ -24,7 +30,9 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{
|
{
|
||||||
string[] uidToken = req.EnterTicket.Split(':');
|
string[] uidToken = req.EnterTicket.Split(':');
|
||||||
|
|
||||||
var account = context.GuestAccounts.SingleOrDefault(x => x.Uid == long.Parse(uidToken[0]) && x.Token == uidToken[1]);
|
var account = context.GuestAccounts.SingleOrDefault(x =>
|
||||||
|
x.Uid == long.Parse(uidToken[0]) && x.Token == uidToken[1]
|
||||||
|
);
|
||||||
|
|
||||||
if (account is null)
|
if (account is null)
|
||||||
{
|
{
|
||||||
|
@ -38,7 +46,9 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
return new AccountCheckYostarResponse()
|
return new AccountCheckYostarResponse()
|
||||||
{
|
{
|
||||||
ResultState = 1,
|
ResultState = 1,
|
||||||
SessionKey = sessionKeyService.Create(account.Uid) ?? new SessionKey() { MxToken = req.EnterTicket }
|
SessionKey =
|
||||||
|
sessionKeyService.Create(account.Uid)
|
||||||
|
?? new SessionKey() { MxToken = req.EnterTicket }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,10 +57,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{
|
{
|
||||||
if (req.SessionKey is null || req.SessionKey.AccountServerId == 0)
|
if (req.SessionKey is null || req.SessionKey.AccountServerId == 0)
|
||||||
{
|
{
|
||||||
return new ErrorPacket()
|
return new ErrorPacket() { ErrorCode = WebAPIErrorCode.AccountAuthNotCreated };
|
||||||
{
|
|
||||||
ErrorCode = WebAPIErrorCode.AccountAuthNotCreated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -69,16 +76,28 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{ OpenConditionContent.Cafe, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Cafe, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Unit_Growth_Skill, OpenConditionLockReason.None },
|
{ OpenConditionContent.Unit_Growth_Skill, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.Unit_Growth_LevelUp, OpenConditionLockReason.None },
|
{ OpenConditionContent.Unit_Growth_LevelUp, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.Unit_Growth_Transcendence, OpenConditionLockReason.None },
|
{
|
||||||
|
OpenConditionContent.Unit_Growth_Transcendence,
|
||||||
|
OpenConditionLockReason.None
|
||||||
|
},
|
||||||
{ OpenConditionContent.WeekDungeon, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.WeekDungeon, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Arena, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Arena, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Academy, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Academy, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Equip, OpenConditionLockReason.None },
|
{ OpenConditionContent.Equip, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.Item, OpenConditionLockReason.None },
|
{ OpenConditionContent.Item, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.Mission, OpenConditionLockReason.None },
|
{ OpenConditionContent.Mission, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.WeekDungeon_Chase, OpenConditionLockReason.StageClear },
|
{
|
||||||
{ OpenConditionContent.__Deprecated_WeekDungeon_FindGift, OpenConditionLockReason.None },
|
OpenConditionContent.WeekDungeon_Chase,
|
||||||
{ OpenConditionContent.__Deprecated_WeekDungeon_Blood, OpenConditionLockReason.None },
|
OpenConditionLockReason.StageClear
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OpenConditionContent.__Deprecated_WeekDungeon_FindGift,
|
||||||
|
OpenConditionLockReason.None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OpenConditionContent.__Deprecated_WeekDungeon_Blood,
|
||||||
|
OpenConditionLockReason.None
|
||||||
|
},
|
||||||
{ OpenConditionContent.Story_Sub, OpenConditionLockReason.None },
|
{ OpenConditionContent.Story_Sub, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.Story_Replay, OpenConditionLockReason.None },
|
{ OpenConditionContent.Story_Replay, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.None, OpenConditionLockReason.None },
|
{ OpenConditionContent.None, OpenConditionLockReason.None },
|
||||||
|
@ -98,24 +117,46 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{ OpenConditionContent.Story, OpenConditionLockReason.None },
|
{ OpenConditionContent.Story, OpenConditionLockReason.None },
|
||||||
{ OpenConditionContent.Tactic_Speed, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Tactic_Speed, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Cafe_Invite, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Cafe_Invite, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Cafe_Invite_2, OpenConditionLockReason.CafeRank | OpenConditionLockReason.StageClear },
|
{
|
||||||
{ OpenConditionContent.EventMiniGame_1, OpenConditionLockReason.StageClear },
|
OpenConditionContent.Cafe_Invite_2,
|
||||||
|
OpenConditionLockReason.CafeRank | OpenConditionLockReason.StageClear
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OpenConditionContent.EventMiniGame_1,
|
||||||
|
OpenConditionLockReason.StageClear
|
||||||
|
},
|
||||||
{ OpenConditionContent.SchoolDungeon, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.SchoolDungeon, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.TimeAttackDungeon, OpenConditionLockReason.StageClear },
|
{
|
||||||
|
OpenConditionContent.TimeAttackDungeon,
|
||||||
|
OpenConditionLockReason.StageClear
|
||||||
|
},
|
||||||
{ OpenConditionContent.ShiftingCraft, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.ShiftingCraft, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Tactic_Skip, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Tactic_Skip, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Mulligan, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Mulligan, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.EventPermanent, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.EventPermanent, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Main_L_1_2, OpenConditionLockReason.ScenarioModeClear },
|
{
|
||||||
{ OpenConditionContent.Main_L_1_3, OpenConditionLockReason.ScenarioModeClear },
|
OpenConditionContent.Main_L_1_2,
|
||||||
{ OpenConditionContent.Main_L_1_4, OpenConditionLockReason.ScenarioModeClear },
|
OpenConditionLockReason.ScenarioModeClear
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OpenConditionContent.Main_L_1_3,
|
||||||
|
OpenConditionLockReason.ScenarioModeClear
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OpenConditionContent.Main_L_1_4,
|
||||||
|
OpenConditionLockReason.ScenarioModeClear
|
||||||
|
},
|
||||||
{ OpenConditionContent.EliminateRaid, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.EliminateRaid, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.Cafe_2, OpenConditionLockReason.StageClear },
|
{ OpenConditionContent.Cafe_2, OpenConditionLockReason.StageClear },
|
||||||
{ OpenConditionContent.MultiFloorRaid, OpenConditionLockReason.StageClear }
|
{ OpenConditionContent.MultiFloorRaid, OpenConditionLockReason.StageClear }
|
||||||
},
|
},
|
||||||
MissionProgressDBs = [.. context.MissionProgresses.Where(x => x.AccountServerId == account.ServerId)]
|
MissionProgressDBs =
|
||||||
|
[
|
||||||
|
.. context.MissionProgresses.Where(x =>
|
||||||
|
x.AccountServerId == account.ServerId
|
||||||
|
)
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,10 +165,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{
|
{
|
||||||
if (req.SessionKey is null)
|
if (req.SessionKey is null)
|
||||||
{
|
{
|
||||||
return new ErrorPacket()
|
return new ErrorPacket() { ErrorCode = WebAPIErrorCode.InvalidSession };
|
||||||
{
|
|
||||||
ErrorCode = WebAPIErrorCode.InvalidSession
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] uidToken = req.SessionKey.MxToken.Split(':');
|
string[] uidToken = req.SessionKey.MxToken.Split(':');
|
||||||
|
@ -137,38 +175,55 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
// Default items
|
// Default items
|
||||||
context.Items.Add(new()
|
context.Items.Add(
|
||||||
{
|
new()
|
||||||
AccountServerId = account.ServerId,
|
{
|
||||||
UniqueId = 2,
|
AccountServerId = account.ServerId,
|
||||||
StackCount = 5
|
UniqueId = 2,
|
||||||
});
|
StackCount = 5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Default chars
|
// Default chars
|
||||||
var defaultCharacters = excelTableService.GetTable<DefaultCharacterExcelTable>().UnPack().DataList;
|
var defaultCharacters = excelTableService
|
||||||
var newCharacters = defaultCharacters.Select(x =>
|
.GetTable<DefaultCharacterExcelTable>()
|
||||||
{
|
.UnPack()
|
||||||
var characterExcel = excelTableService.GetTable<CharacterExcelTable>().UnPack().DataList.Find(y => y.Id == x.CharacterId);
|
.DataList;
|
||||||
|
var newCharacters = defaultCharacters
|
||||||
return new CharacterDB()
|
.Select(x =>
|
||||||
{
|
{
|
||||||
UniqueId = x.CharacterId,
|
var characterExcel = excelTableService
|
||||||
StarGrade = x.StarGrade,
|
.GetTable<CharacterExcelTable>()
|
||||||
Level = x.Level,
|
.UnPack()
|
||||||
Exp = x.Exp,
|
.DataList.Find(y => y.Id == x.CharacterId);
|
||||||
FavorRank = x.FavorRank,
|
|
||||||
FavorExp = x.FavorExp,
|
return new CharacterDB()
|
||||||
PublicSkillLevel = 1,
|
{
|
||||||
ExSkillLevel = x.ExSkillLevel,
|
UniqueId = x.CharacterId,
|
||||||
PassiveSkillLevel = x.PassiveSkillLevel,
|
StarGrade = x.StarGrade,
|
||||||
ExtraPassiveSkillLevel = x.ExtraPassiveSkillLevel,
|
Level = x.Level,
|
||||||
LeaderSkillLevel = x.LeaderSkillLevel,
|
Exp = x.Exp,
|
||||||
IsNew = true,
|
FavorRank = x.FavorRank,
|
||||||
IsLocked = true,
|
FavorExp = x.FavorExp,
|
||||||
EquipmentServerIds = characterExcel is not null ? characterExcel.EquipmentSlot.Select(x => (long)0).ToList() : [0, 0, 0],
|
PublicSkillLevel = 1,
|
||||||
PotentialStats = { { 1, 0 }, { 2, 0 }, { 3, 0 } }
|
ExSkillLevel = x.ExSkillLevel,
|
||||||
};
|
PassiveSkillLevel = x.PassiveSkillLevel,
|
||||||
}).ToList();
|
ExtraPassiveSkillLevel = x.ExtraPassiveSkillLevel,
|
||||||
|
LeaderSkillLevel = x.LeaderSkillLevel,
|
||||||
|
IsNew = true,
|
||||||
|
IsLocked = true,
|
||||||
|
EquipmentServerIds = characterExcel is not null
|
||||||
|
? characterExcel.EquipmentSlot.Select(x => (long)0).ToList()
|
||||||
|
: [0, 0, 0],
|
||||||
|
PotentialStats =
|
||||||
|
{
|
||||||
|
{ 1, 0 },
|
||||||
|
{ 2, 0 },
|
||||||
|
{ 3, 0 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
account.AddCharacters(context, [.. newCharacters]);
|
account.AddCharacters(context, [.. newCharacters]);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
@ -176,19 +231,26 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
var favCharacter = defaultCharacters.Find(x => x.FavoriteCharacter);
|
var favCharacter = defaultCharacters.Find(x => x.FavoriteCharacter);
|
||||||
if (favCharacter is not null)
|
if (favCharacter is not null)
|
||||||
{
|
{
|
||||||
account.RepresentCharacterServerId = (int)newCharacters.First(x => x.UniqueId == favCharacter.CharacterId).ServerId;
|
account.RepresentCharacterServerId = (int)
|
||||||
|
newCharacters.First(x => x.UniqueId == favCharacter.CharacterId).ServerId;
|
||||||
}
|
}
|
||||||
if (newCharacters.Count > 0)
|
if (newCharacters.Count > 0)
|
||||||
{
|
{
|
||||||
context.Echelons.Add(new()
|
context.Echelons.Add(
|
||||||
{
|
new()
|
||||||
AccountServerId = account.ServerId,
|
{
|
||||||
EchelonNumber = 1,
|
AccountServerId = account.ServerId,
|
||||||
EchelonType = EchelonType.Adventure,
|
EchelonNumber = 1,
|
||||||
LeaderServerId = newCharacters[0].ServerId,
|
EchelonType = EchelonType.Adventure,
|
||||||
MainSlotServerIds = newCharacters.Take(3).Select(x => x.ServerId).Append(0).ToList(),
|
LeaderServerId = newCharacters[0].ServerId,
|
||||||
SupportSlotServerIds = [0, 0]
|
MainSlotServerIds = newCharacters
|
||||||
});
|
.Take(3)
|
||||||
|
.Select(x => x.ServerId)
|
||||||
|
.Append(0)
|
||||||
|
.ToList(),
|
||||||
|
SupportSlotServerIds = [0, 0]
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
|
@ -206,17 +268,14 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
account.Nickname = req.Nickname;
|
account.Nickname = req.Nickname;
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
return new AccountNicknameResponse()
|
return new AccountNicknameResponse() { AccountDB = account };
|
||||||
{
|
|
||||||
AccountDB = account
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.Account_LoginSync)]
|
[ProtocolHandler(Protocol.Account_LoginSync)]
|
||||||
public ResponsePacket LoginSyncHandler(AccountLoginSyncRequest req)
|
public ResponsePacket LoginSyncHandler(AccountLoginSyncRequest req)
|
||||||
{
|
{
|
||||||
var account = sessionKeyService.GetAccount(req.SessionKey);
|
var account = sessionKeyService.GetAccount(req.SessionKey);
|
||||||
|
ArgumentNullException.ThrowIfNull(account);
|
||||||
|
|
||||||
return new AccountLoginSyncResponse()
|
return new AccountLoginSyncResponse()
|
||||||
{
|
{
|
||||||
|
@ -224,7 +283,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{
|
{
|
||||||
AccountCurrencyDB = new AccountCurrencyDB
|
AccountCurrencyDB = new AccountCurrencyDB
|
||||||
{
|
{
|
||||||
AccountLevel = 1,
|
AccountLevel = 90,
|
||||||
AcademyLocationRankSum = 1,
|
AcademyLocationRankSum = 1,
|
||||||
CurrencyDict = new Dictionary<CurrencyTypes, long>
|
CurrencyDict = new Dictionary<CurrencyTypes, long>
|
||||||
{
|
{
|
||||||
|
@ -260,23 +319,71 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{ CurrencyTypes.AcademyTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
{ CurrencyTypes.AcademyTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
{ CurrencyTypes.ArenaTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
{ CurrencyTypes.ArenaTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
{ CurrencyTypes.RaidTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
{ CurrencyTypes.RaidTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
{ CurrencyTypes.WeekDungeonChaserATicket, DateTime.Parse("2024-04-26T19:29:12") },
|
{
|
||||||
{ CurrencyTypes.WeekDungeonChaserBTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
CurrencyTypes.WeekDungeonChaserATicket,
|
||||||
{ CurrencyTypes.WeekDungeonChaserCTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
{ CurrencyTypes.SchoolDungeonATicket, DateTime.Parse("2024-04-26T19:29:12") },
|
},
|
||||||
{ CurrencyTypes.SchoolDungeonBTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
{
|
||||||
{ CurrencyTypes.SchoolDungeonCTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
CurrencyTypes.WeekDungeonChaserBTicket,
|
||||||
{ CurrencyTypes.TimeAttackDungeonTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.WeekDungeonChaserCTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonATicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonBTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonCTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.TimeAttackDungeonTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
{ CurrencyTypes.MasterCoin, DateTime.Parse("2024-04-26T19:29:12") },
|
{ CurrencyTypes.MasterCoin, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
{ CurrencyTypes.WorldRaidTicketA, DateTime.Parse("2024-04-26T19:29:12") },
|
{
|
||||||
{ CurrencyTypes.WorldRaidTicketB, DateTime.Parse("2024-04-26T19:29:12") },
|
CurrencyTypes.WorldRaidTicketA,
|
||||||
{ CurrencyTypes.WorldRaidTicketC, DateTime.Parse("2024-04-26T19:29:12") },
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
{ CurrencyTypes.ChaserTotalTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
},
|
||||||
{ CurrencyTypes.SchoolDungeonTotalTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
{
|
||||||
{ CurrencyTypes.EliminateTicketA, DateTime.Parse("2024-04-26T19:29:12") },
|
CurrencyTypes.WorldRaidTicketB,
|
||||||
{ CurrencyTypes.EliminateTicketB, DateTime.Parse("2024-04-26T19:29:12") },
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
{ CurrencyTypes.EliminateTicketC, DateTime.Parse("2024-04-26T19:29:12") },
|
},
|
||||||
{ CurrencyTypes.EliminateTicketD, DateTime.Parse("2024-04-26T19:29:12") }
|
{
|
||||||
|
CurrencyTypes.WorldRaidTicketC,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.ChaserTotalTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonTotalTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.EliminateTicketA,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.EliminateTicketB,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.EliminateTicketC,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.EliminateTicketD,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -287,10 +394,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
WeaponDBs = [.. account.Weapons],
|
WeaponDBs = [.. account.Weapons],
|
||||||
CostumeDBs = [],
|
CostumeDBs = [],
|
||||||
},
|
},
|
||||||
ItemListResponse = new ItemListResponse()
|
ItemListResponse = new ItemListResponse() { ItemDBs = [.. account.Items], },
|
||||||
{
|
|
||||||
ItemDBs = [.. account.Items],
|
|
||||||
},
|
|
||||||
|
|
||||||
EchelonListResponse = new EchelonListResponse()
|
EchelonListResponse = new EchelonListResponse()
|
||||||
{
|
{
|
||||||
|
@ -298,7 +402,8 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
},
|
},
|
||||||
EventContentPermanentListResponse = new EventContentPermanentListResponse()
|
EventContentPermanentListResponse = new EventContentPermanentListResponse()
|
||||||
{
|
{
|
||||||
PermanentDBs = [
|
PermanentDBs =
|
||||||
|
[
|
||||||
new() { EventContentId = 900801 },
|
new() { EventContentId = 900801 },
|
||||||
new() { EventContentId = 900802 },
|
new() { EventContentId = 900802 },
|
||||||
new() { EventContentId = 900803 },
|
new() { EventContentId = 900803 },
|
||||||
|
@ -327,10 +432,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
|
|
||||||
ClanLoginResponse = new ClanLoginResponse()
|
ClanLoginResponse = new ClanLoginResponse()
|
||||||
{
|
{
|
||||||
AccountClanMemberDB = new()
|
AccountClanMemberDB = new() { AccountId = account.ServerId }
|
||||||
{
|
|
||||||
AccountId = account.ServerId
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
EliminateRaidLoginResponse = new EliminateRaidLoginResponse()
|
EliminateRaidLoginResponse = new EliminateRaidLoginResponse()
|
||||||
|
@ -350,11 +452,15 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
[ProtocolHandler(Protocol.Account_GetTutorial)]
|
[ProtocolHandler(Protocol.Account_GetTutorial)]
|
||||||
public ResponsePacket GetTutorialHandler(AccountGetTutorialRequest req)
|
public ResponsePacket GetTutorialHandler(AccountGetTutorialRequest req)
|
||||||
{
|
{
|
||||||
var tutorialIds = context.AccountTutorials.SingleOrDefault(x => x.AccountServerId == sessionKeyService.GetAccount(req.SessionKey).ServerId)?.TutorialIds;
|
var tutorialIds = context
|
||||||
|
.AccountTutorials.SingleOrDefault(x =>
|
||||||
|
x.AccountServerId == sessionKeyService.GetAccount(req.SessionKey).ServerId
|
||||||
|
)
|
||||||
|
?.TutorialIds;
|
||||||
|
|
||||||
return new AccountGetTutorialResponse()
|
return new AccountGetTutorialResponse()
|
||||||
{
|
{
|
||||||
TutorialIds = tutorialIds ?? []
|
TutorialIds = tutorialIds ?? Enumerable.Range(1, 27).Select(i => (long)i).ToList()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,7 +468,9 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
public ResponsePacket SetTutorialHandler(AccountSetTutorialRequest req)
|
public ResponsePacket SetTutorialHandler(AccountSetTutorialRequest req)
|
||||||
{
|
{
|
||||||
var account = sessionKeyService.GetAccount(req.SessionKey);
|
var account = sessionKeyService.GetAccount(req.SessionKey);
|
||||||
var tutorial = context.AccountTutorials.SingleOrDefault(x => x.AccountServerId == account.ServerId);
|
var tutorial = context.AccountTutorials.SingleOrDefault(x =>
|
||||||
|
x.AccountServerId == account.ServerId
|
||||||
|
);
|
||||||
if (tutorial == null)
|
if (tutorial == null)
|
||||||
{
|
{
|
||||||
tutorial = new()
|
tutorial = new()
|
||||||
|
@ -401,45 +509,48 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
[ProtocolHandler(Protocol.Toast_List)]
|
[ProtocolHandler(Protocol.Toast_List)]
|
||||||
public ResponsePacket ToastListHandler(ToastListRequest req)
|
public ResponsePacket ToastListHandler(ToastListRequest req)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new ToastListResponse();
|
return new ToastListResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.ContentLog_UIOpenStatistics)]
|
[ProtocolHandler(Protocol.ContentLog_UIOpenStatistics)]
|
||||||
public ResponsePacket ContentLog_UIOpenStatisticsHandler(ContentLogUIOpenStatisticsRequest req)
|
public ResponsePacket ContentLog_UIOpenStatisticsHandler(
|
||||||
|
ContentLogUIOpenStatisticsRequest req
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new ContentLogUIOpenStatisticsResponse();
|
return new ContentLogUIOpenStatisticsResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.Event_RewardIncrease)]
|
[ProtocolHandler(Protocol.Event_RewardIncrease)]
|
||||||
public ResponsePacket Event_RewardIncreaseHandler(EventRewardIncreaseRequest req)
|
public ResponsePacket Event_RewardIncreaseHandler(EventRewardIncreaseRequest req)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new EventRewardIncreaseResponse();
|
return new EventRewardIncreaseResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.OpenCondition_EventList)]
|
[ProtocolHandler(Protocol.OpenCondition_EventList)]
|
||||||
public ResponsePacket OpenCondition_EventListHandler(OpenConditionEventListRequest req)
|
public ResponsePacket OpenCondition_EventListHandler(OpenConditionEventListRequest req)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new OpenConditionEventListResponse()
|
return new OpenConditionEventListResponse()
|
||||||
{
|
{
|
||||||
// all open for now ig
|
// all open for now ig
|
||||||
StaticOpenConditions = Enum.GetValues(typeof(OpenConditionContent)).Cast<OpenConditionContent>().ToDictionary(key => key, key => OpenConditionLockReason.None)
|
StaticOpenConditions = Enum.GetValues(typeof(OpenConditionContent))
|
||||||
|
.Cast<OpenConditionContent>()
|
||||||
|
.ToDictionary(key => key, key => OpenConditionLockReason.None)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.Notification_EventContentReddotCheck)]
|
[ProtocolHandler(Protocol.Notification_EventContentReddotCheck)]
|
||||||
public ResponsePacket Notification_EventContentReddotCheckHandler(NotificationEventContentReddotRequest req)
|
public ResponsePacket Notification_EventContentReddotCheckHandler(
|
||||||
|
NotificationEventContentReddotRequest req
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return new NotificationEventContentReddotResponse();
|
return new NotificationEventContentReddotResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.Billing_PurchaseListByYostar)]
|
[ProtocolHandler(Protocol.Billing_PurchaseListByYostar)]
|
||||||
public ResponsePacket Billing_PurchaseListByYostarHandler(BillingPurchaseListByYostarRequest req)
|
public ResponsePacket Billing_PurchaseListByYostarHandler(
|
||||||
|
BillingPurchaseListByYostarRequest req
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
return new BillingPurchaseListByYostarResponse();
|
return new BillingPurchaseListByYostarResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,5 +572,4 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
return new MiniGameMissionListResponse();
|
return new MiniGameMissionListResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,327 @@
|
||||||
|
using SCHALE.Common.Database;
|
||||||
|
using SCHALE.Common.FlatData;
|
||||||
|
using SCHALE.Common.NetworkProtocol;
|
||||||
|
using SCHALE.GameServer.Services;
|
||||||
|
|
||||||
|
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
|
{
|
||||||
|
public class Arena(
|
||||||
|
IProtocolHandlerFactory protocolHandlerFactory,
|
||||||
|
ISessionKeyService _sessionKeyService,
|
||||||
|
SCHALEContext _context
|
||||||
|
) : ProtocolHandlerBase(protocolHandlerFactory)
|
||||||
|
{
|
||||||
|
private readonly ISessionKeyService sessionKeyService = _sessionKeyService;
|
||||||
|
private readonly SCHALEContext context = _context;
|
||||||
|
|
||||||
|
private EquipmentDB? GetEquipmentDB(long accountServerId, long equipmentServerId)
|
||||||
|
{
|
||||||
|
if (equipmentServerId == 0)
|
||||||
|
return null;
|
||||||
|
return context.Equipment.First(c =>
|
||||||
|
c.AccountServerId == accountServerId && c.ServerId == equipmentServerId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArenaCharacterDB? Convert(long accountServerId, long characterServerId)
|
||||||
|
{
|
||||||
|
if (characterServerId == 0)
|
||||||
|
return null;
|
||||||
|
var characterDB = context.Characters.First(c =>
|
||||||
|
c.AccountServerId == accountServerId && c.ServerId == characterServerId
|
||||||
|
);
|
||||||
|
return Convert(characterDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArenaCharacterDB Convert(CharacterDB db)
|
||||||
|
{
|
||||||
|
var res = new ArenaCharacterDB
|
||||||
|
{
|
||||||
|
UniqueId = db.UniqueId,
|
||||||
|
StarGrade = db.StarGrade,
|
||||||
|
Level = db.Level,
|
||||||
|
PublicSkillLevel = db.PublicSkillLevel,
|
||||||
|
ExSkillLevel = db.ExSkillLevel,
|
||||||
|
PassiveSkillLevel = db.PassiveSkillLevel,
|
||||||
|
ExtraPassiveSkillLevel = db.ExtraPassiveSkillLevel,
|
||||||
|
LeaderSkillLevel = db.LeaderSkillLevel,
|
||||||
|
EquipmentDBs = db
|
||||||
|
.EquipmentServerIds.Select(i => GetEquipmentDB(db.AccountServerId, i))
|
||||||
|
.Where(i => i != null)
|
||||||
|
.ToList()!,
|
||||||
|
FavorRankInfo = new Dictionary<long, long>
|
||||||
|
{
|
||||||
|
// TODO: add all
|
||||||
|
{ db.UniqueId, db.FavorRank }
|
||||||
|
},
|
||||||
|
PotentialStats = db.PotentialStats
|
||||||
|
};
|
||||||
|
var weaponDB = context.Weapons.FirstOrDefault(w =>
|
||||||
|
w.AccountServerId == db.AccountServerId && w.BoundCharacterServerId == db.ServerId
|
||||||
|
);
|
||||||
|
if (weaponDB != null)
|
||||||
|
res.WeaponDB = weaponDB;
|
||||||
|
var gearDB = context.Gears.FirstOrDefault(w =>
|
||||||
|
w.AccountServerId == db.AccountServerId && w.BoundCharacterServerId == db.ServerId
|
||||||
|
);
|
||||||
|
if (gearDB != null)
|
||||||
|
res.GearDB = gearDB;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArenaTeamSettingDB Convert(EchelonDB db)
|
||||||
|
{
|
||||||
|
var LeaderCharacterId = context
|
||||||
|
.Characters.First(c =>
|
||||||
|
c.AccountServerId == db.AccountServerId && c.ServerId == db.LeaderServerId
|
||||||
|
)
|
||||||
|
.UniqueId;
|
||||||
|
|
||||||
|
return new ArenaTeamSettingDB()
|
||||||
|
{
|
||||||
|
EchelonType = db.EchelonType,
|
||||||
|
LeaderCharacterId = LeaderCharacterId,
|
||||||
|
MainCharacters = db
|
||||||
|
.MainSlotServerIds.Select(i => Convert(db.AccountServerId, i))
|
||||||
|
.Where(i => i != null)
|
||||||
|
.ToList()!,
|
||||||
|
SupportCharacters = db
|
||||||
|
.SupportSlotServerIds.Select(i => Convert(db.AccountServerId, i))
|
||||||
|
.Where(i => i != null)
|
||||||
|
.ToList()!,
|
||||||
|
MapId = 1001,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly ArenaTeamSettingDB dummyTeam =
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
EchelonType = EchelonType.ArenaDefence,
|
||||||
|
LeaderCharacterId = 10065,
|
||||||
|
MainCharacters =
|
||||||
|
[
|
||||||
|
new ArenaCharacterDB()
|
||||||
|
{
|
||||||
|
UniqueId = 10065,
|
||||||
|
StarGrade = 3,
|
||||||
|
Level = 90,
|
||||||
|
PublicSkillLevel = 1,
|
||||||
|
ExSkillLevel = 1,
|
||||||
|
PassiveSkillLevel = 1,
|
||||||
|
ExtraPassiveSkillLevel = 1,
|
||||||
|
LeaderSkillLevel = 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
MapId = 1001,
|
||||||
|
};
|
||||||
|
|
||||||
|
private ArenaTeamSettingDB? GetDefense(long accountId)
|
||||||
|
{
|
||||||
|
var defense = context.Echelons.FirstOrDefault(e =>
|
||||||
|
e.AccountServerId == accountId
|
||||||
|
&& e.EchelonType == EchelonType.ArenaDefence
|
||||||
|
&& e.EchelonNumber == 1
|
||||||
|
&& e.ExtensionType == EchelonExtensionType.Base
|
||||||
|
);
|
||||||
|
if (defense == null)
|
||||||
|
return null;
|
||||||
|
return Convert(defense);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ArenaUserDB> DummyOpponent(ArenaTeamSettingDB? team)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new ArenaUserDB()
|
||||||
|
{
|
||||||
|
RepresentCharacterUniqueId = 20024,
|
||||||
|
NickName = "your",
|
||||||
|
Rank = 2,
|
||||||
|
Level = 90,
|
||||||
|
TeamSettingDB = team ?? dummyTeam
|
||||||
|
},
|
||||||
|
new ArenaUserDB()
|
||||||
|
{
|
||||||
|
RepresentCharacterUniqueId = 10059,
|
||||||
|
NickName = "defense",
|
||||||
|
Rank = 3,
|
||||||
|
Level = 90,
|
||||||
|
TeamSettingDB = team ?? dummyTeam
|
||||||
|
},
|
||||||
|
new ArenaUserDB()
|
||||||
|
{
|
||||||
|
RepresentCharacterUniqueId = 10065,
|
||||||
|
NickName = "team",
|
||||||
|
Rank = 4,
|
||||||
|
Level = 90,
|
||||||
|
TeamSettingDB = team ?? dummyTeam
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtocolHandler(Protocol.Arena_EnterLobby)]
|
||||||
|
public ResponsePacket EnterLobbyHandler(ArenaEnterLobbyRequest req)
|
||||||
|
{
|
||||||
|
return new ArenaEnterLobbyResponse()
|
||||||
|
{
|
||||||
|
ArenaPlayerInfoDB = new()
|
||||||
|
{
|
||||||
|
CurrentSeasonId = 6,
|
||||||
|
PlayerGroupId = 1,
|
||||||
|
CurrentRank = 1,
|
||||||
|
SeasonRecord = 1,
|
||||||
|
AllTimeRecord = 1
|
||||||
|
},
|
||||||
|
OpponentUserDBs = DummyOpponent(GetDefense(req.AccountId)),
|
||||||
|
MapId = 1001
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtocolHandler(Protocol.Arena_OpponentList)]
|
||||||
|
public ResponsePacket OpponentListHandler(ArenaOpponentListRequest req)
|
||||||
|
{
|
||||||
|
return new ArenaOpponentListResponse()
|
||||||
|
{
|
||||||
|
PlayerRank = 1,
|
||||||
|
OpponentUserDBs = DummyOpponent(GetDefense(req.AccountId))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtocolHandler(Protocol.Arena_SyncEchelonSettingTime)]
|
||||||
|
public ResponsePacket SyncEchelonSettingTimeHandler(ArenaSyncEchelonSettingTimeRequest req)
|
||||||
|
{
|
||||||
|
return new ArenaSyncEchelonSettingTimeResponse() { EchelonSettingTime = DateTime.Now };
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtocolHandler(Protocol.Arena_EnterBattlePart1)]
|
||||||
|
public ResponsePacket EnterBattlePart1Handler(ArenaEnterBattlePart1Request req)
|
||||||
|
{
|
||||||
|
var attack = context.Echelons.First(e =>
|
||||||
|
e.AccountServerId == req.AccountId
|
||||||
|
&& e.EchelonType == EchelonType.ArenaAttack
|
||||||
|
&& e.EchelonNumber == 1
|
||||||
|
&& e.ExtensionType == EchelonExtensionType.Base
|
||||||
|
);
|
||||||
|
|
||||||
|
ArenaUserDB arenaUserDB =
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
RepresentCharacterUniqueId = 10059,
|
||||||
|
NickName = "You",
|
||||||
|
Rank = 1,
|
||||||
|
Level = 90,
|
||||||
|
TeamSettingDB = Convert(attack)
|
||||||
|
};
|
||||||
|
return new ArenaEnterBattlePart1Response()
|
||||||
|
{
|
||||||
|
ArenaBattleDB = new()
|
||||||
|
{
|
||||||
|
Season = 6,
|
||||||
|
Group = 1,
|
||||||
|
BattleStartTime = DateTime.Now,
|
||||||
|
Seed = 1,
|
||||||
|
AttackingUserDB = arenaUserDB,
|
||||||
|
DefendingUserDB = DummyOpponent(GetDefense(req.AccountId))[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtocolHandler(Protocol.Arena_EnterBattlePart2)]
|
||||||
|
public ResponsePacket EnterBattlePart2Handler(ArenaEnterBattlePart2Request req)
|
||||||
|
{
|
||||||
|
return new ArenaEnterBattlePart2Response()
|
||||||
|
{
|
||||||
|
ArenaBattleDB = req.ArenaBattleDB,
|
||||||
|
ArenaPlayerInfoDB = new ArenaPlayerInfoDB()
|
||||||
|
{
|
||||||
|
CurrentSeasonId = 6,
|
||||||
|
PlayerGroupId = 1,
|
||||||
|
CurrentRank = 1,
|
||||||
|
SeasonRecord = 1,
|
||||||
|
AllTimeRecord = 1
|
||||||
|
},
|
||||||
|
AccountCurrencyDB = new AccountCurrencyDB
|
||||||
|
{
|
||||||
|
AccountLevel = 90,
|
||||||
|
AcademyLocationRankSum = 1,
|
||||||
|
CurrencyDict = new Dictionary<CurrencyTypes, long>
|
||||||
|
{
|
||||||
|
{ CurrencyTypes.Gem, long.MaxValue }, // gacha currency 600
|
||||||
|
{ CurrencyTypes.GemPaid, 0 },
|
||||||
|
{ CurrencyTypes.GemBonus, long.MaxValue }, // default blue gem?
|
||||||
|
{ CurrencyTypes.Gold, 962_350_000 }, // credit 10,000
|
||||||
|
{ CurrencyTypes.ActionPoint, long.MaxValue }, // energy 24
|
||||||
|
{ CurrencyTypes.AcademyTicket, 3 },
|
||||||
|
{ CurrencyTypes.ArenaTicket, 5 },
|
||||||
|
{ CurrencyTypes.RaidTicket, 3 },
|
||||||
|
{ CurrencyTypes.WeekDungeonChaserATicket, 0 },
|
||||||
|
{ CurrencyTypes.WeekDungeonChaserBTicket, 0 },
|
||||||
|
{ CurrencyTypes.WeekDungeonChaserCTicket, 0 },
|
||||||
|
{ CurrencyTypes.SchoolDungeonATicket, 0 },
|
||||||
|
{ CurrencyTypes.SchoolDungeonBTicket, 0 },
|
||||||
|
{ CurrencyTypes.SchoolDungeonCTicket, 0 },
|
||||||
|
{ CurrencyTypes.TimeAttackDungeonTicket, 3 },
|
||||||
|
{ CurrencyTypes.MasterCoin, 0 },
|
||||||
|
{ CurrencyTypes.WorldRaidTicketA, 40 },
|
||||||
|
{ CurrencyTypes.WorldRaidTicketB, 40 },
|
||||||
|
{ CurrencyTypes.WorldRaidTicketC, 40 },
|
||||||
|
{ CurrencyTypes.ChaserTotalTicket, 6 },
|
||||||
|
{ CurrencyTypes.SchoolDungeonTotalTicket, 6 },
|
||||||
|
{ CurrencyTypes.EliminateTicketA, 1 },
|
||||||
|
{ CurrencyTypes.EliminateTicketB, 1 },
|
||||||
|
{ CurrencyTypes.EliminateTicketC, 1 },
|
||||||
|
{ CurrencyTypes.EliminateTicketD, 1 }
|
||||||
|
},
|
||||||
|
UpdateTimeDict = new Dictionary<CurrencyTypes, DateTime>
|
||||||
|
{
|
||||||
|
{ CurrencyTypes.ActionPoint, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.AcademyTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.ArenaTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.RaidTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{
|
||||||
|
CurrencyTypes.WeekDungeonChaserATicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.WeekDungeonChaserBTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.WeekDungeonChaserCTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonATicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonBTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonCTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CurrencyTypes.TimeAttackDungeonTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{ CurrencyTypes.MasterCoin, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.WorldRaidTicketA, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.WorldRaidTicketB, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.WorldRaidTicketC, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.ChaserTotalTicket, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{
|
||||||
|
CurrencyTypes.SchoolDungeonTotalTicket,
|
||||||
|
DateTime.Parse("2024-04-26T19:29:12")
|
||||||
|
},
|
||||||
|
{ CurrencyTypes.EliminateTicketA, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.EliminateTicketB, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.EliminateTicketC, DateTime.Parse("2024-04-26T19:29:12") },
|
||||||
|
{ CurrencyTypes.EliminateTicketD, DateTime.Parse("2024-04-26T19:29:12") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using SCHALE.Common.Database;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SCHALE.Common.Database;
|
||||||
using SCHALE.Common.Database.ModelExtensions;
|
using SCHALE.Common.Database.ModelExtensions;
|
||||||
using SCHALE.Common.FlatData;
|
using SCHALE.Common.FlatData;
|
||||||
using SCHALE.Common.NetworkProtocol;
|
using SCHALE.Common.NetworkProtocol;
|
||||||
|
@ -6,37 +7,36 @@ using SCHALE.GameServer.Services;
|
||||||
|
|
||||||
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
|
||||||
{
|
{
|
||||||
public class Echelon : ProtocolHandlerBase
|
public class Echelon(
|
||||||
|
IProtocolHandlerFactory protocolHandlerFactory,
|
||||||
|
ISessionKeyService _sessionKeyService,
|
||||||
|
SCHALEContext _context,
|
||||||
|
ExcelTableService _excelTableService,
|
||||||
|
ILogger<Echelon> _logger
|
||||||
|
) : ProtocolHandlerBase(protocolHandlerFactory)
|
||||||
{
|
{
|
||||||
private readonly ISessionKeyService sessionKeyService;
|
private readonly ISessionKeyService sessionKeyService = _sessionKeyService;
|
||||||
private readonly SCHALEContext context;
|
private readonly SCHALEContext context = _context;
|
||||||
private readonly ExcelTableService excelTableService;
|
private readonly ExcelTableService excelTableService = _excelTableService;
|
||||||
|
private readonly ILogger<Echelon> logger = _logger;
|
||||||
public Echelon(IProtocolHandlerFactory protocolHandlerFactory, ISessionKeyService _sessionKeyService, SCHALEContext _context, ExcelTableService _excelTableService) : base(protocolHandlerFactory)
|
|
||||||
{
|
|
||||||
sessionKeyService = _sessionKeyService;
|
|
||||||
context = _context;
|
|
||||||
excelTableService = _excelTableService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.Echelon_List)]
|
[ProtocolHandler(Protocol.Echelon_List)]
|
||||||
public ResponsePacket ListHandler(EchelonListRequest req)
|
public ResponsePacket ListHandler(EchelonListRequest req)
|
||||||
{
|
{
|
||||||
var account = sessionKeyService.GetAccount(req.SessionKey);
|
var account = sessionKeyService.GetAccount(req.SessionKey);
|
||||||
|
|
||||||
return new EchelonListResponse()
|
return new EchelonListResponse() { EchelonDBs = [.. account.Echelons] };
|
||||||
{
|
|
||||||
EchelonDBs = account.Echelons.ToList()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ProtocolHandler(Protocol.Echelon_Save)]
|
[ProtocolHandler(Protocol.Echelon_Save)]
|
||||||
public ResponsePacket SaveHandler(EchelonSaveRequest req)
|
public ResponsePacket SaveHandler(EchelonSaveRequest req)
|
||||||
{
|
{
|
||||||
return new EchelonSaveResponse()
|
var db = req.EchelonDB;
|
||||||
{
|
|
||||||
EchelonDB = req.EchelonDB,
|
context.Echelons.Add(db);
|
||||||
};
|
context.SaveChanges();
|
||||||
|
|
||||||
|
return new EchelonSaveResponse() { EchelonDB = req.EchelonDB, };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SCHALE.Common.Crypto;
|
||||||
|
using SCHALE.Common.NetworkProtocol;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SCHALE.GameServer.Controllers.Data
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("/data")]
|
||||||
|
public class DataController : ControllerBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly string absFolder;
|
||||||
|
private readonly ILogger<DataController> logger;
|
||||||
|
public DataController(ILogger<DataController> _logger)
|
||||||
|
{
|
||||||
|
logger = _logger;
|
||||||
|
|
||||||
|
var folder = Path.GetDirectoryName(AppContext.BaseDirectory);
|
||||||
|
string dataFolder;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
dataFolder = Path.Join(folder, "Resources/data");
|
||||||
|
if (Directory.Exists(dataFolder)) break;
|
||||||
|
folder = Path.GetDirectoryName(folder);
|
||||||
|
if (folder == null)
|
||||||
|
throw new FileNotFoundException($"Excel folder is not found.");
|
||||||
|
}
|
||||||
|
absFolder = dataFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string? AbsPath(string relPath)
|
||||||
|
{
|
||||||
|
string filePath = Path.Combine(absFolder, relPath);
|
||||||
|
if (!System.IO.File.Exists(filePath)) return null;
|
||||||
|
logger.LogDebug($"Use our own {relPath}.");
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("TableBundles/{fileName}")]
|
||||||
|
public IActionResult GetTableBundles(string fileName)
|
||||||
|
{
|
||||||
|
string relPath = $"TableBundles/{fileName}";
|
||||||
|
string? filePath = AbsPath(relPath);
|
||||||
|
if (filePath == null) return CatchAll(relPath);
|
||||||
|
|
||||||
|
if (fileName.EndsWith(".json"))
|
||||||
|
{
|
||||||
|
var jsonContent = System.IO.File.ReadAllText(filePath);
|
||||||
|
return Content(jsonContent, "application/json");
|
||||||
|
}
|
||||||
|
if (fileName.EndsWith(".zip"))
|
||||||
|
{
|
||||||
|
var fileStream = System.IO.File.OpenRead(filePath);
|
||||||
|
return File(fileStream, "application/zip", fileName);
|
||||||
|
}
|
||||||
|
if (fileName.EndsWith(".bytes"))
|
||||||
|
{
|
||||||
|
var fileStream = System.IO.File.OpenRead(filePath);
|
||||||
|
return File(fileStream, "application/octet-stream", fileName);
|
||||||
|
}
|
||||||
|
return CatchAll(relPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{*catchAll}")]
|
||||||
|
public IActionResult CatchAll(string catchAll)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Data path: {path}", catchAll);
|
||||||
|
string re = $"https://prod-clientpatch.bluearchiveyostar.com/r68_10iazxytt13razwn7x9n_3/{catchAll}";
|
||||||
|
return Redirect(re);
|
||||||
|
// return RedirectPermanent(re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,48 @@
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using System.Collections;
|
||||||
using Serilog.Events;
|
using System.Net.NetworkInformation;
|
||||||
using Serilog;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SCHALE.Common.Crypto;
|
||||||
using SCHALE.Common.Database;
|
using SCHALE.Common.Database;
|
||||||
|
using SCHALE.GameServer.Commands;
|
||||||
using SCHALE.GameServer.Controllers.Api.ProtocolHandlers;
|
using SCHALE.GameServer.Controllers.Api.ProtocolHandlers;
|
||||||
using SCHALE.GameServer.Services;
|
using SCHALE.GameServer.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using SCHALE.GameServer.Services.Irc;
|
using SCHALE.GameServer.Services.Irc;
|
||||||
using SCHALE.GameServer.Commands;
|
|
||||||
using SCHALE.GameServer.Utils;
|
using SCHALE.GameServer.Utils;
|
||||||
using System.Net.NetworkInformation;
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace SCHALE.GameServer
|
namespace SCHALE.GameServer
|
||||||
{
|
{
|
||||||
public class GameServer
|
public class GameServer
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
var config = new ConfigurationBuilder()
|
var config = new ConfigurationBuilder()
|
||||||
.SetBasePath(Path.GetDirectoryName(AppContext.BaseDirectory)!)
|
.SetBasePath(Path.GetDirectoryName(AppContext.BaseDirectory)!)
|
||||||
.AddJsonFile("appsettings.json")
|
.AddJsonFile("appsettings.json")
|
||||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
|
.AddJsonFile(
|
||||||
|
$"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json",
|
||||||
|
true
|
||||||
|
)
|
||||||
.AddJsonFile("appsettings.Local.json", true)
|
.AddJsonFile("appsettings.Local.json", true)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
{
|
{
|
||||||
var logFilePath = Path.Combine(Path.GetDirectoryName(AppContext.BaseDirectory)!, "logs", "log.txt");
|
var logFilePath = Path.Combine(
|
||||||
|
Path.GetDirectoryName(AppContext.BaseDirectory)!,
|
||||||
|
"logs",
|
||||||
|
"log.txt"
|
||||||
|
);
|
||||||
|
|
||||||
if (File.Exists(logFilePath))
|
if (File.Exists(logFilePath))
|
||||||
{
|
{
|
||||||
var prevLogFilePath = Path.Combine(Path.GetDirectoryName(logFilePath)!, "log-prev.txt");
|
var prevLogFilePath = Path.Combine(
|
||||||
|
Path.GetDirectoryName(logFilePath)!,
|
||||||
|
"log-prev.txt"
|
||||||
|
);
|
||||||
if (File.Exists(prevLogFilePath))
|
if (File.Exists(prevLogFilePath))
|
||||||
File.Delete(prevLogFilePath);
|
File.Delete(prevLogFilePath);
|
||||||
|
|
||||||
|
@ -37,15 +50,22 @@ namespace SCHALE.GameServer
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.WriteTo.Console()
|
.WriteTo.Console()
|
||||||
.WriteTo.File(logFilePath, restrictedToMinimumLevel: LogEventLevel.Verbose, shared: true)
|
.WriteTo.File(
|
||||||
.ReadFrom.Configuration(config)
|
logFilePath,
|
||||||
.CreateBootstrapLogger();
|
restrictedToMinimumLevel: LogEventLevel.Verbose,
|
||||||
|
shared: true
|
||||||
|
)
|
||||||
|
.ReadFrom.Configuration(config)
|
||||||
|
.CreateBootstrapLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("Starting...");
|
Log.Information("Starting...");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Log.Information("Downloading Excels...");
|
||||||
|
await ExcelTableService.Init();
|
||||||
|
|
||||||
// Load Commands
|
// Load Commands
|
||||||
CommandFactory.LoadCommands();
|
CommandFactory.LoadCommands();
|
||||||
|
|
||||||
|
@ -54,17 +74,36 @@ namespace SCHALE.GameServer
|
||||||
|
|
||||||
if (Config.Instance.Address == "127.0.0.1")
|
if (Config.Instance.Address == "127.0.0.1")
|
||||||
{
|
{
|
||||||
Config.Instance.Address = NetworkInterface.GetAllNetworkInterfaces().Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback && i.OperationalStatus == OperationalStatus.Up).First().GetIPProperties().UnicastAddresses.Where(a => a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).First().Address.ToString();
|
Config.Instance.Address = NetworkInterface
|
||||||
|
.GetAllNetworkInterfaces()
|
||||||
|
.Where(i =>
|
||||||
|
i.NetworkInterfaceType != NetworkInterfaceType.Loopback
|
||||||
|
&& i.OperationalStatus == OperationalStatus.Up
|
||||||
|
)
|
||||||
|
.First()
|
||||||
|
.GetIPProperties()
|
||||||
|
.UnicastAddresses.Where(a =>
|
||||||
|
a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork
|
||||||
|
)
|
||||||
|
.First()
|
||||||
|
.Address.ToString();
|
||||||
Config.Save();
|
Config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.Configure<KestrelServerOptions>(op => op.AllowSynchronousIO = true);
|
builder.Services.Configure<KestrelServerOptions>(op =>
|
||||||
|
op.AllowSynchronousIO = true
|
||||||
|
);
|
||||||
builder.Host.UseSerilog();
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddSQLServerProvider(config.GetConnectionString("SQLServer") ?? throw new ArgumentNullException("ConnectionStrings/SQLServer in appsettings is missing"));
|
builder.Services.AddSQLServerProvider(
|
||||||
|
config.GetConnectionString("SQLServer")
|
||||||
|
?? throw new ArgumentNullException(
|
||||||
|
"ConnectionStrings/SQLServer in appsettings is missing"
|
||||||
|
)
|
||||||
|
);
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddProtocolHandlerFactory();
|
builder.Services.AddProtocolHandlerFactory();
|
||||||
builder.Services.AddMemorySessionKeyService();
|
builder.Services.AddMemorySessionKeyService();
|
||||||
|
@ -72,8 +111,11 @@ namespace SCHALE.GameServer
|
||||||
builder.Services.AddIrcService();
|
builder.Services.AddIrcService();
|
||||||
|
|
||||||
// Add all Handler Groups
|
// Add all Handler Groups
|
||||||
var handlerGroups = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(ProtocolHandlerBase)));
|
var handlerGroups = Assembly
|
||||||
|
.GetExecutingAssembly()
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.IsSubclassOf(typeof(ProtocolHandlerBase)));
|
||||||
|
|
||||||
foreach (var handlerGroup in handlerGroups)
|
foreach (var handlerGroup in handlerGroups)
|
||||||
builder.Services.AddProtocolHandlerGroupByType(handlerGroup);
|
builder.Services.AddProtocolHandlerGroupByType(handlerGroup);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DotNetZip" Version="1.16.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|
|
@ -1,18 +1,79 @@
|
||||||
using Google.FlatBuffers;
|
using System.IO;
|
||||||
using SCHALE.Common.Crypto;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Google.FlatBuffers;
|
||||||
|
using Ionic.Zip;
|
||||||
|
using SCHALE.Common.Crypto;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace SCHALE.GameServer.Services
|
namespace SCHALE.GameServer.Services
|
||||||
{
|
{
|
||||||
// TODO: High priority, cache UnPack-ed table!
|
// TODO: High priority, cache UnPack-ed table!
|
||||||
public class ExcelTableService
|
public class ExcelTableService(ILogger<ExcelTableService> _logger)
|
||||||
{
|
{
|
||||||
private readonly ILogger<ExcelTableService> logger;
|
private readonly ILogger<ExcelTableService> logger = _logger;
|
||||||
private readonly Dictionary<Type, object> caches = [];
|
private readonly Dictionary<Type, object> caches = [];
|
||||||
|
|
||||||
public ExcelTableService(ILogger<ExcelTableService> _logger)
|
private static string? ResourcesFolder;
|
||||||
|
private static string? ExcelFolder;
|
||||||
|
|
||||||
|
private static string GetUrl()
|
||||||
{
|
{
|
||||||
logger = _logger;
|
string urlPath;
|
||||||
|
if (ResourcesFolder == null)
|
||||||
|
{
|
||||||
|
var folder = Path.GetDirectoryName(AppContext.BaseDirectory);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
urlPath = Path.Join(folder, "Resources/url.txt");
|
||||||
|
if (File.Exists(urlPath))
|
||||||
|
break;
|
||||||
|
folder = Path.GetDirectoryName(folder);
|
||||||
|
if (folder == null)
|
||||||
|
throw new FileNotFoundException($"Resources folder is not found.");
|
||||||
|
}
|
||||||
|
ResourcesFolder = Path.GetDirectoryName(urlPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
urlPath = Path.Join(ResourcesFolder, "url.txt");
|
||||||
|
}
|
||||||
|
string url = File.ReadAllText(urlPath);
|
||||||
|
return url + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task GetZip()
|
||||||
|
{
|
||||||
|
|
||||||
|
string url = GetUrl();
|
||||||
|
string filePath = "TableBundles/Excel.zip";
|
||||||
|
string zipPath = Path.Combine(ResourcesFolder!, "download", filePath);
|
||||||
|
|
||||||
|
ExcelFolder = zipPath[..^4];
|
||||||
|
if (File.Exists(zipPath))
|
||||||
|
return;
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!);
|
||||||
|
|
||||||
|
using HttpClient client = new();
|
||||||
|
HttpResponseMessage response = await client.GetAsync(url + filePath);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
byte[] content = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
File.WriteAllBytes(zipPath, content);
|
||||||
|
using ZipFile zip = ZipFile.Read(zipPath);
|
||||||
|
//zip.Password = "/wy5f3hIGGXLOIUDS9DZ";
|
||||||
|
zip.Password = Convert.ToBase64String(TableService.CreatePassword(Path.GetFileName(filePath)));
|
||||||
|
|
||||||
|
foreach (ZipEntry e in zip)
|
||||||
|
{
|
||||||
|
e.Extract(ExcelFolder, ExtractExistingFileAction.OverwriteSilently);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Init()
|
||||||
|
{
|
||||||
|
await GetZip();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -21,14 +82,16 @@ namespace SCHALE.GameServer.Services
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="FileNotFoundException"></exception>
|
/// <exception cref="FileNotFoundException"></exception>
|
||||||
public T GetTable<T>() where T : IFlatbufferObject
|
public T GetTable<T>()
|
||||||
|
where T : IFlatbufferObject
|
||||||
{
|
{
|
||||||
var type = typeof(T);
|
var type = typeof(T);
|
||||||
|
|
||||||
if (caches.TryGetValue(type, out var cache))
|
if (caches.TryGetValue(type, out var cache))
|
||||||
return (T)cache;
|
return (T)cache;
|
||||||
|
|
||||||
var bytesFilePath = Path.Join(Path.GetDirectoryName(AppContext.BaseDirectory), "Resources/excel/", $"{type.Name.ToLower()}.bytes");
|
ArgumentNullException.ThrowIfNull(ExcelFolder);
|
||||||
|
var bytesFilePath = Path.Join(ExcelFolder, $"{type.Name.ToLower()}.bytes");
|
||||||
if (!File.Exists(bytesFilePath))
|
if (!File.Exists(bytesFilePath))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException($"bytes files for {type.Name} not found");
|
throw new FileNotFoundException($"bytes files for {type.Name} not found");
|
||||||
|
@ -36,7 +99,12 @@ namespace SCHALE.GameServer.Services
|
||||||
|
|
||||||
var bytes = File.ReadAllBytes(bytesFilePath);
|
var bytes = File.ReadAllBytes(bytesFilePath);
|
||||||
TableEncryptionService.XOR(type.Name, bytes);
|
TableEncryptionService.XOR(type.Name, bytes);
|
||||||
var inst = type.GetMethod($"GetRootAs{type.Name}", BindingFlags.Static | BindingFlags.Public, [typeof(ByteBuffer)])!.Invoke(null, [new ByteBuffer(bytes)]);
|
var inst = type.GetMethod(
|
||||||
|
$"GetRootAs{type.Name}",
|
||||||
|
BindingFlags.Static | BindingFlags.Public,
|
||||||
|
[typeof(ByteBuffer)]
|
||||||
|
)!
|
||||||
|
.Invoke(null, [new ByteBuffer(bytes)]);
|
||||||
|
|
||||||
caches.Add(type, inst!);
|
caches.Add(type, inst!);
|
||||||
logger.LogDebug("{Excel} loaded and cached", type.Name);
|
logger.LogDebug("{Excel} loaded and cached", type.Name);
|
||||||
|
|
|
@ -79,8 +79,8 @@ namespace SCHALE.Common.Utils
|
||||||
UniqueId = x.UniqueId,
|
UniqueId = x.UniqueId,
|
||||||
BoundCharacterServerId = x.ServerId,
|
BoundCharacterServerId = x.ServerId,
|
||||||
IsLocked = false,
|
IsLocked = false,
|
||||||
StarGrade = 5,
|
StarGrade = 3,
|
||||||
Level = 200
|
Level = 50
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -132,17 +132,17 @@ namespace SCHALE.Common.Utils
|
||||||
{
|
{
|
||||||
UniqueId = characterId,
|
UniqueId = characterId,
|
||||||
StarGrade = 5,
|
StarGrade = 5,
|
||||||
Level = 200,
|
Level = 90,
|
||||||
Exp = 0,
|
Exp = 0,
|
||||||
PublicSkillLevel = 10,
|
PublicSkillLevel = 10,
|
||||||
ExSkillLevel = 5,
|
ExSkillLevel = 5,
|
||||||
PassiveSkillLevel = 10,
|
PassiveSkillLevel = 10,
|
||||||
ExtraPassiveSkillLevel = 10,
|
ExtraPassiveSkillLevel = 10,
|
||||||
LeaderSkillLevel = 1,
|
LeaderSkillLevel = 1,
|
||||||
FavorRank = 500,
|
FavorRank = 20,
|
||||||
IsNew = true,
|
IsNew = true,
|
||||||
IsLocked = true,
|
IsLocked = true,
|
||||||
PotentialStats = { { 1, 25 }, { 2, 25 }, { 3, 25 } },
|
PotentialStats = { { 1, 0 }, { 2, 0 }, { 3, 0 } },
|
||||||
EquipmentServerIds = [0, 0, 0]
|
EquipmentServerIds = [0, 0, 0]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue