First push: build.zig

First push:   build.zig.zon
	First push:   config.json
	First push:   dispatch/build.zig
	First push:   dispatch/build.zig.zon
	First push:   dispatch/src/authentication.zig
	First push:   dispatch/src/dispatch.zig
	First push:   dispatch/src/hotfix.zig
	First push:   dispatch/src/main.zig
	First push:   gameserver/build.zig
	First push:   gameserver/build.zig.zon
	First push:   gameserver/src/Packet.zig
	First push:   gameserver/src/Session.zig
	First push:   gameserver/src/command.zig
	First push:   gameserver/src/commands/help.zig
	First push:   gameserver/src/commands/refill.zig
	First push:   gameserver/src/commands/sync.zig
	First push:   gameserver/src/commands/tp.zig
	First push:   gameserver/src/commands/unstuck.zig
	First push:   gameserver/src/commands/value.zig
	First push:   gameserver/src/data.zig
	First push:   gameserver/src/handlers.zig
	First push:   gameserver/src/main.zig
	First push:   gameserver/src/manager/battle_mgr.zig
	First push:   gameserver/src/manager/lineup_mgr.zig
	First push:   gameserver/src/manager/multipath_mgr.zig
	First push:   gameserver/src/manager/scene_mgr.zig
	First push:   gameserver/src/network.zig
	First push:   gameserver/src/services/avatar.zig
	First push:   gameserver/src/services/battle.zig
	First push:   gameserver/src/services/challenge.zig
	First push:   gameserver/src/services/chat.zig
	First push:   gameserver/src/services/config.zig
	First push:   gameserver/src/services/events.zig
	First push:   gameserver/src/services/gacha.zig
	First push:   gameserver/src/services/item.zig
	First push:   gameserver/src/services/lineup.zig
	First push:   gameserver/src/services/login.zig
	First push:   gameserver/src/services/mail.zig
	First push:   gameserver/src/services/misc copy.zig
	First push:   gameserver/src/services/misc.zig
	First push:   gameserver/src/services/mission.zig
	First push:   gameserver/src/services/multipath.zig
	First push:   gameserver/src/services/pet.zig
	First push:   gameserver/src/services/profile.zig
	First push:   gameserver/src/services/res_config.zig
	First push:   gameserver/src/services/scene.zig
	First push:   hotfix.json
	First push:   protocol/build.zig
	First push:   protocol/build.zig.zon
	First push:   protocol/src/protocol.pb.zig
	First push:   protocol/src/root.zig
	First push:   resources/ChallengeMazeConfig.json
	First push:   resources/MapEntrance.json
	First push:   resources/MazePlane.json
	First push:   resources/res.json
This commit is contained in:
HuLiNap 2025-04-17 16:16:03 +07:00
parent ab26c9ba2c
commit 78e23046f7
56 changed files with 563840 additions and 0 deletions

64
build.zig Normal file
View file

@ -0,0 +1,64 @@
const std = @import("std");
const protobuf = @import("protobuf");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const dep_opts = .{ .target = target, .optimize = optimize };
const protobuf_dep = b.dependency("protobuf", dep_opts);
if (std.fs.cwd().access("protocol/StarRail.proto", .{})) {
const protoc_step = protobuf.RunProtocStep.create(b, protobuf_dep.builder, target, .{
.destination_directory = b.path("protocol/src"),
.source_files = &.{
"protocol/StarRail.proto",
},
.include_directories = &.{},
});
b.getInstallStep().dependOn(&protoc_step.step);
} else |_| {} // don't invoke protoc if proto definition doesn't exist
const dispatch = b.dependency("dispatch", dep_opts);
b.installArtifact(dispatch.artifact("dispatch"));
const gameserver = b.dependency("gameserver", dep_opts);
b.installArtifact(gameserver.artifact("gameserver"));
// "run-dispatch" command
const dispatch_cmd = b.addRunArtifact(dispatch.artifact("dispatch"));
dispatch_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
dispatch_cmd.addArgs(args);
}
const dispatch_step = b.step("run-dispatch", "Run the dispatch server");
dispatch_step.dependOn(&dispatch_cmd.step);
// "run-gameserver" command
const gameserver_cmd = b.addRunArtifact(gameserver.artifact("gameserver"));
gameserver_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
gameserver_cmd.addArgs(args);
}
const gameserver_step = b.step("run-gameserver", "Run the game server");
gameserver_step.dependOn(&gameserver_cmd.step);
// "gen-proto"
const gen_proto = b.step("gen-proto", "generates zig files from protocol buffer definitions");
const protoc_step = protobuf.RunProtocStep.create(b, protobuf_dep.builder, target, .{
// out directory for the generated zig files
.destination_directory = b.path("protocol/src"),
.source_files = &.{
"protocol/StarRail.proto",
},
.include_directories = &.{},
});
gen_proto.dependOn(&protoc_step.step);
}

15
build.zig.zon Normal file
View file

@ -0,0 +1,15 @@
.{
.name = "CipherSR",
.version = "0.0.1",
.minimum_zig_version = "0.13.0",
.dependencies = .{
.dispatch = .{ .path = "dispatch" },
.gameserver = .{ .path = "gameserver" },
.protocol = .{ .path = "protocol" },
.protobuf = .{
.url = "https://github.com/Arwalk/zig-protobuf/archive/7c49ed66e029c9c7e6253b3d6d256118745550a4.tar.gz",
.hash = "122063ee7ff32a3c1aefd91a46a9fc23df0571949c3a02e2f44d39afbad0b53018a3",
},
},
.paths = .{""},
}

117
config.json Normal file
View file

@ -0,0 +1,117 @@
{
"avatar_config": [
{
"name": "Cipher",
"id": 1406,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 23043,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61261,15,1,4,8:1:1,3:4:4,7:2:2,4:2:2",
"61262,15,1,4,10:1:1,1:3:3,9:4:4,8:1:1",
"61263,15,5,4,2:3:3,4:2:2,12:2:2,6:2:2",
"61264,15,4,4,6:2:2,2:1:1,1:4:4,5:2:2",
"63145,15,9,4,9:1:1,3:2:2,7:4:4,5:2:2",
"63146,15,4,4,1:2:2,3:3:3,4:2:2,2:2:2"
],
"use_technique": true
},
{
"name": "Hyacine",
"id": 1409,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 23042,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61251,15,1,4,6:1:1,11:2:2,5:2:2,2:4:4",
"61252,15,1,4,5:3:3,4:1:1,7:2:2,10:3:3",
"61253,15,5,4,3:2:2,11:2:2,10:3:3,5:2:2",
"61254,15,4,4,11:3:3,9:3:3,12:1:1,4:2:2",
"63205,15,1,4,3:1:1,8:4:4,9:2:2,2:2:2",
"63206,15,2,4,6:3:3,8:3:3,11:1:1,3:2:2"
],
"use_technique": true
},
{
"name": "Trailblazer",
"id": 8008,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 23042,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61261,15,1,4,12:3:3,11:2:2,8:3:3,2:1:1",
"61262,15,1,4,3:3:3,8:2:2,6:2:2,1:2:2",
"61263,15,5,4,12:1:1,5:2:2,7:3:3,4:3:3",
"61264,15,4,4,6:1:1,9:3:3,2:3:3,1:2:2",
"63145,15,6,4,7:4:4,8:1:1,3:2:2,12:2:2",
"63146,15,2,4,8:2:2,6:3:3,10:3:3,1:1:1"
],
"use_technique": true
},
{
"name": "Acheron",
"id": 1308,
"hp": 100,
"sp": 50,
"level": 80,
"promotion": 6,
"rank": 6,
"lightcone": {
"id": 23024,
"rank": 5,
"level": 80,
"promotion": 6
},
"relics": [
"61171,15,1,4,9:2:2,12:2:2,8:3:3,6:2:2",
"61172,15,1,4,8:4:4,4:3:3,5:1:1,6:1:1",
"61173,15,5,4,3:1:1,12:3:3,11:3:3,7:2:2",
"61174,15,4,4,5:1:1,11:2:2,4:4:4,6:2:2",
"63145,15,7,4,9:2:2,4:1:1,6:2:2,2:4:4",
"63146,15,4,4,4:2:2,7:3:3,1:2:2,2:2:2"
],
"use_technique": true
}
],
"battle_config": {
"battle_id": 1,
"stage_id": 30114122,
"cycle_count": 30,
"monster_wave": [
[
4033010,
4033030,
4032030
],
[
2034010
]
],
"monster_level": 95,
"blessings": []
}
}

41
dispatch/build.zig Normal file
View file

@ -0,0 +1,41 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const httpz = b.dependency("httpz", .{
.optimize = optimize,
.target = target,
});
const protocol = b.dependency("protocol", .{});
const exe = b.addExecutable(.{
.name = "dispatch",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const tls12 = b.dependency("tls12", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("tls12", tls12.module("zig-tls12"));
exe.root_module.addImport("httpz", httpz.module("httpz"));
exe.root_module.addImport("protocol", protocol.module("protocol"));
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

23
dispatch/build.zig.zon Normal file
View file

@ -0,0 +1,23 @@
.{
.name = "dispatch",
.version = "0.0.1",
.minimum_zig_version = "0.13.0",
.dependencies = .{
.httpz = .{
.url = "https://github.com/karlseguin/http.zig/archive/44b97984c6a9613634e238d27ca6cdff4d4abebd.tar.gz",
.hash = "122034afde1b4ff5971787929d1a4a8ef175e1c18fe0cf4ffc1af1ce0c95c7b6be7b",
},
.protocol = .{
.path = "../protocol",
},
.tls12 = .{
.url = "https://github.com/melonedo/zig-tls12/archive/f2cbb84.tar.gz",
.hash = "12202bb2c3824deecd4ccd666ca3dcb9c92c2c68698afdb92b382c0f5cb1b86da8bc",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

View file

@ -0,0 +1,219 @@
const std = @import("std");
const httpz = @import("httpz");
pub fn onShieldLogin(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onShieldLogin: {any}", .{req.body_len});
//std.log.debug("onShieldLogin BODY: {any}", .{req}); game breaking
try res.json(.{
.data = .{
.account = .{
.area_code = "**",
.email = "ReversedRoooms@StarRail.com",
.country = "RU",
.is_email_verify = "1",
.token = "aa",
.uid = "1337",
},
.device_grant_required = false,
.reactivate_required = false,
.realperson_required = false,
.safe_mobilerequired = false,
},
.message = "OK",
.retcode = 0,
}, .{});
}
pub fn onShieldVerify(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onShieldVerify: {any}", .{req.body_len});
try res.json(.{
.data = .{
.account = .{
.area_code = "**",
.email = "ReversedRoooms@StarRail.com",
.country = "RU",
.is_email_verify = "1",
.token = "aa",
.uid = "1337",
},
.device_grant_required = false,
.reactivate_required = false,
.realperson_required = false,
.safe_mobilerequired = false,
},
.message = "OK",
.retcode = 0,
}, .{});
}
pub fn onVerifyLogin(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onVerifyLogin: {any}", .{req.body_len});
var token: []const u8 = "aa";
var uid: []const u8 = "1337";
if (try req.jsonObject()) |t| {
if (t.get("token")) |token_value| {
token = token_value.string;
}
if (t.get("uid")) |uid_value| {
uid = uid_value.string;
}
}
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.account = .{
.area_code = "**",
.country = "CN",
.is_email_verify = "1",
.email = "ReversedRoooms@StarRail.com",
.token = token,
.uid = uid,
},
},
}, .{});
}
pub fn onComboTokenReq(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onComboTokenReq: {any}", .{req.body_len});
try res.json(.{
.data = .{
.account_type = 1,
.open_id = "1337",
.combo_id = "1337",
.combo_token = "9065ad8507d5a1991cb6fddacac5999b780bbd92",
.heartbeat = false,
.data = "{\"guest\": false}",
},
.message = "OK",
.retcode = 0,
}, .{});
}
pub fn onRiskyApiCheck(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onRiskyApiCheck: {any}", .{req.body_len});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.id = "06611ed14c3131a676b19c0d34c0644b",
.action = "ACTION_NONE",
.geetest = null,
},
}, .{});
}
pub fn onGetConfig(_: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onGetConfig: ", .{});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.protocol = true,
.qr_enabled = false,
.log_level = "INFO",
.announce_url = "",
.push_alias_type = 0,
.disable_ysdk_guard = true,
.enable_announce_pic_popup = false,
.app_name = "<EFBFBD>??RPG",
.qr_enabled_apps = .{
.bbs = false,
.cloud = false,
},
.qr_app_icons = .{
.app = "",
.bbs = "",
.cloud = "",
},
.qr_cloud_display_name = "",
.enable_user_center = true,
.functional_switch_configs = .{},
},
}, .{});
}
pub fn onLoadConfig(_: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onLoadConfig: ", .{});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.id = 24,
.game_key = "hkrpg_global",
.client = "PC",
.identity = "I_IDENTITY",
.guest = false,
.ignore_versions = "",
.scene = "S_NORMAL",
.name = "<EFBFBD>??RPG",
.disable_regist = false,
.enable_email_captcha = false,
.thirdparty = .{ "fb", "tw", "gl", "ap" },
.disable_mmt = false,
.server_guest = false,
.thirdparty_ignore = .{},
.enable_ps_bind_account = false,
.thirdparty_login_configs = .{
.tw = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 2592000,
},
.ap = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 604800,
},
.fb = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 2592000,
},
.gl = .{
.token_type = "TK_GAME_TOKEN",
.game_token_expires_in = 604800,
},
},
.initialize_firebase = false,
.bbs_auth_login = false,
.bbs_auth_login_ignore = {},
.fetch_instance_id = false,
.enable_flash_login = false,
},
}, .{});
}
pub fn onappLoginByPassword(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onappLoginByPassword: {any}", .{req.body_len});
try res.json(.{
.retcode = 0,
.message = "OK",
.data = .{
.bind_email_action_ticket = "",
.ext_user_info = .{
.birth = "0",
.guardian_email = "",
},
.reactivate_action_token = "",
.token = .{
.token = "aa",
.token_type = "1",
},
.user_info = .{
.account_name = "ReversedRooms",
.aid = "1337",
.area_code = "**",
.country = "RU",
.email = "ReversedRoooms@StarRail.com",
.is_email_verify = "1",
},
},
}, .{});
}

213
dispatch/src/dispatch.zig Normal file
View file

@ -0,0 +1,213 @@
const std = @import("std");
const httpz = @import("httpz");
const protocol = @import("protocol");
const HttpClient = @import("tls12");
const Base64Encoder = @import("std").base64.standard.Encoder;
const Base64Decoder = @import("std").base64.standard.Decoder;
const hotfixInfo = @import("hotfix.zig");
const CNPROD_HOST = "prod-gf-cn-dp01.bhsr.com";
const CNBETA_HOST = "beta-release01-cn.bhsr.com";
const OSPROD_HOST = "prod-official-asia-dp01.starrails.com";
const OSBETA_HOST = "beta-release01-asia.starrails.com";
const OSCE_HOST = "alb-ftpc0bk5jk0c9b3go4.us-east-1.alb.aliyuncs.com";
const CNCE_HOST = "alb-xvofr71l6rrvwgcupw.cn-shanghai.alb.aliyuncs.com";
pub fn onQueryDispatch(_: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onQueryDispatch", .{});
var proto = protocol.Dispatch.init(res.arena);
const region_info = protocol.RegionInfo{
.name = .{ .Const = "CipherSR" },
.display_name = .{ .Const = "CipherSR" },
.env_type = .{ .Const = "21" },
.title = .{ .Const = "CipherSR" },
.dispatch_url = .{ .Const = "http://127.0.0.1:21000/query_gateway" },
};
try proto.region_list.append(region_info);
const data = try proto.encode(res.arena);
const size = Base64Encoder.calcSize(data.len);
const output = try res.arena.alloc(u8, size);
_ = Base64Encoder.encode(output, data);
res.body = output;
}
pub fn onQueryGateway(req: *httpz.Request, res: *httpz.Response) !void {
std.log.debug("onQueryGateway", .{});
var proto = protocol.Gateserver.init(res.arena);
const query = try req.query();
const version = query.get("version") orelse "";
std.log.info("Get Version >> {s}", .{version});
const dispatch_seed = query.get("dispatch_seed") orelse "";
std.log.info("Get DispatchSeed >> {s}", .{dispatch_seed});
const host = selectHost(version);
var gatewayUrl: []const u8 = undefined;
//Check if the client is CE version
if (std.mem.startsWith(u8, version, "OSCE") or std.mem.startsWith(u8, version, "CNCE")) {
gatewayUrl = constructCEUrl(host, version, dispatch_seed);
std.log.info("Constructed CE Gateway URL >> {s}", .{gatewayUrl});
} else {
gatewayUrl = constructUrl(host, version, dispatch_seed);
std.log.info("Constructed Gateway URL >> {s}", .{gatewayUrl});
}
const hotfix = try hotfixInfo.Parser(res.arena, "hotfix.json", version);
var assetBundleUrl: []const u8 = undefined;
var exResourceUrl: []const u8 = undefined;
var luaUrl: []const u8 = undefined;
var iFixUrl: []const u8 = undefined;
//var luaVersion: []const u8 = undefined;
//var iFixVersion: []const u8 = undefined;
//HTTP Request
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var client = HttpClient{ .allocator = allocator };
defer client.deinit();
try client.initDefaultProxies(allocator);
const url = gatewayUrl;
const uri = try std.Uri.parse(url);
var server_header_buffer: [1024 * 1024]u8 = undefined;
var gateway_request = try HttpClient.open(&client, .GET, uri, .{
.server_header_buffer = &server_header_buffer,
.redirect_behavior = @enumFromInt(10),
});
defer gateway_request.deinit();
try gateway_request.send();
try gateway_request.wait();
const gateway_response_body = try gateway_request.reader().readAllAlloc(allocator, 16 * 1024 * 1024);
//Base64 Decode
const decoded_len = try Base64Decoder.calcSizeForSlice(gateway_response_body);
const decoded_data = try allocator.alloc(u8, decoded_len);
defer allocator.free(decoded_data);
try Base64Decoder.decode(decoded_data, gateway_response_body);
//Gateserver Protobuf Decode
const gateserver_proto = try protocol.Gateserver.decode(decoded_data, res.arena);
//std.log.info("\x1b[33;1mEncoded Gateway Response >> {s}\x1b[0m", .{gateway_response_body});
//std.log.info("\x1b[32;1mDecoded Gateway Response >> {s}\x1b[0m", .{decoded_data});
//std.log.info("\x1b[33;1mProtobuf Message >> {}\x1b[0m", .{gateserver_proto});
assetBundleUrl = hotfix.assetBundleUrl;
//Check if the client is CE version
if (std.mem.startsWith(u8, version, "OSCE") or std.mem.startsWith(u8, version, "CNCE")) {
exResourceUrl = "";
} else {
exResourceUrl = hotfix.exResourceUrl;
}
luaUrl = hotfix.luaUrl;
iFixUrl = hotfix.iFixUrl;
if (assetBundleUrl.len == 0 or exResourceUrl.len == 0 or luaUrl.len == 0 or iFixUrl.len == 0) {
assetBundleUrl = gateserver_proto.asset_bundle_url.Owned.str;
//Check if the client is CE version
if (std.mem.startsWith(u8, version, "OSCE") or std.mem.startsWith(u8, version, "CNCE")) {
exResourceUrl = "";
} else {
exResourceUrl = gateserver_proto.ex_resource_url.Owned.str;
}
luaUrl = gateserver_proto.lua_url.Owned.str;
iFixUrl = gateserver_proto.ifix_url.Owned.str;
try hotfixInfo.putValue(version, assetBundleUrl, exResourceUrl, luaUrl, iFixUrl);
} else {
// Check if exResourceUrl has changed
const latestExResourceUrl = gateserver_proto.ex_resource_url.Owned.str;
if (!std.mem.eql(u8, exResourceUrl, latestExResourceUrl)) {
std.log.info("exResourceUrl changed from '{s}' to '{s}'", .{ exResourceUrl, latestExResourceUrl });
assetBundleUrl = gateserver_proto.asset_bundle_url.Owned.str;
//Check if the client is CE version
if (std.mem.startsWith(u8, version, "OSCE") or std.mem.startsWith(u8, version, "CNCE")) {
exResourceUrl = "";
} else {
exResourceUrl = latestExResourceUrl;
}
luaUrl = gateserver_proto.lua_url.Owned.str;
iFixUrl = gateserver_proto.ifix_url.Owned.str;
try hotfixInfo.putValue(version, assetBundleUrl, exResourceUrl, luaUrl, iFixUrl);
}
}
std.log.info("Get AssetBundleUrl >> {s}", .{assetBundleUrl});
std.log.info("Get ExResourceUrl >> {s}", .{exResourceUrl});
std.log.info("Get LuaUrl >> {s}", .{luaUrl});
std.log.info("Get IFixUrl >> {s}", .{iFixUrl});
proto.retcode = 0;
proto.port = 23301;
proto.ip = .{ .Const = "127.0.0.1" };
proto.asset_bundle_url = .{ .Const = assetBundleUrl };
proto.ex_resource_url = .{ .Const = exResourceUrl };
proto.lua_url = .{ .Const = luaUrl };
proto.enable_watermark = true;
proto.network_diagnostic = true;
proto.enable_android_middle_package = true;
proto.use_new_networking = true;
//Check if the client is CE version
if (std.mem.startsWith(u8, version, "OSCE") or std.mem.startsWith(u8, version, "CNCE")) {
proto.enable_design_data_version_update = false;
proto.enable_version_update = false;
std.log.info("CE client version {s} detected, skipping design_data download.", .{version});
} else {
proto.enable_design_data_version_update = true;
proto.enable_version_update = true;
}
proto.mtp_switch = true;
proto.forbid_recharge = true;
proto.close_redeem_code = true;
proto.ECBFEHFPOFJ = false;
proto.enable_save_replay_file = true;
proto.ios_exam = true;
proto.event_tracking_open = true;
proto.use_tcp = true;
proto.enable_upload_battle_log = false;
const data = try proto.encode(res.arena);
const size = Base64Encoder.calcSize(data.len);
const output = try res.arena.alloc(u8, size);
_ = Base64Encoder.encode(output, data);
res.body = output;
}
pub fn selectHost(version: []const u8) []const u8 {
if (std.mem.startsWith(u8, version, "CNPROD")) {
return CNPROD_HOST;
} else if (std.mem.startsWith(u8, version, "CNBETA")) {
return CNBETA_HOST;
} else if (std.mem.startsWith(u8, version, "OSPROD")) {
return OSPROD_HOST;
} else if (std.mem.startsWith(u8, version, "OSBETA")) {
return OSBETA_HOST;
} else if (std.mem.startsWith(u8, version, "OSCE")) {
return OSCE_HOST;
} else if (std.mem.startsWith(u8, version, "CNCE")) {
return CNCE_HOST;
} else {
return "";
}
}
pub fn constructUrl(host: []const u8, version: []const u8, dispatch_seed: []const u8) []const u8 {
return std.fmt.allocPrint(std.heap.page_allocator, "https://{s}/query_gateway?version={s}&dispatch_seed={s}&language_type=1&platform_type=2&channel_id=1&sub_channel_id=1&is_need_url=1&account_type=1", .{ host, version, dispatch_seed }) catch "";
}
pub fn constructCEUrl(host: []const u8, version: []const u8, dispatch_seed: []const u8) []const u8 {
return std.fmt.allocPrint(std.heap.page_allocator, "http://{s}/query_gateway?version={s}&dispatch_seed={s}&language_type=1&platform_type=2&channel_id=1&sub_channel_id=1&is_need_url=1&account_type=1", .{ host, version, dispatch_seed }) catch "";
}

95
dispatch/src/hotfix.zig Normal file
View file

@ -0,0 +1,95 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const hotfixInfo = struct {
clientVersion: []const u8,
assetBundleUrl: []const u8,
exResourceUrl: []const u8,
luaUrl: []const u8,
iFixUrl: []const u8,
};
pub fn Parser(allocator: Allocator, filename: []const u8, version: []const u8) !hotfixInfo {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
const buffer = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer, .{ .ignore_unknown_fields = true });
defer json_tree.deinit();
const version_node = json_tree.value.object.get(version) orelse {
return hotfixInfo{
.clientVersion = version,
.assetBundleUrl = "",
.exResourceUrl = "",
.luaUrl = "",
.iFixUrl = "",
};
};
return getValue(version_node, version);
}
fn getValue(node: std.json.Value, client_version: []const u8) !hotfixInfo {
if (node != .object) return error.InvalidJsonStructure;
const obj = node.object;
const assetBundleUrl = obj.get("asset_bundle_url") orelse return error.MissingAssetBundleUrl;
const exResourceUrl = obj.get("ex_resource_url") orelse return error.MissingExResourceUrl;
const luaUrl = obj.get("lua_url") orelse return error.MissingLuaUrl;
const iFixUrl = obj.get("ifix_url") orelse return error.MissingIFixUrl;
if (assetBundleUrl != .string or
exResourceUrl != .string or
luaUrl != .string or
iFixUrl != .string) return error.InvalidUrlFormat;
return hotfixInfo{
.clientVersion = client_version,
.assetBundleUrl = assetBundleUrl.string,
.exResourceUrl = exResourceUrl.string,
.luaUrl = luaUrl.string,
.iFixUrl = iFixUrl.string,
};
}
pub fn putValue(version: []const u8, assetBundleUrl: []const u8, exResourceUrl: []const u8, luaUrl: []const u8, iFixUrl: []const u8) !void {
const file = try std.fs.cwd().openFile("hotfix.json", .{ .mode = .read_write });
defer file.close();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const file_size = try file.getEndPos();
const buffer0 = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer0);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer0, .{ .ignore_unknown_fields = true });
defer json_tree.deinit();
var root = json_tree.value.object;
var new_version = std.json.ObjectMap.init(allocator);
try new_version.put("asset_bundle_url", .{ .string = assetBundleUrl });
try new_version.put("ex_resource_url", .{ .string = exResourceUrl });
try new_version.put("ifix_url", .{ .string = iFixUrl });
try new_version.put("ifix_version", .{ .string = "0" });
try new_version.put("lua_url", .{ .string = luaUrl });
try new_version.put("lua_version", .{ .string = "" });
try root.put(version, .{ .object = new_version });
const json_value = std.json.Value{ .object = root };
var buffer = std.ArrayList(u8).init(allocator);
try std.json.stringify(json_value, .{ .whitespace = .indent_4 }, buffer.writer());
const new_file = try std.fs.cwd().createFile("hotfix.json", .{ .truncate = true });
defer new_file.close();
try new_file.writeAll(buffer.items);
}

35
dispatch/src/main.zig Normal file
View file

@ -0,0 +1,35 @@
const std = @import("std");
const builtin = @import("builtin");
const httpz = @import("httpz");
const protocol = @import("protocol");
const authentication = @import("authentication.zig");
const dispatch = @import("dispatch.zig");
const PORT = 21000;
pub const std_options = .{
.log_level = switch (builtin.mode) {
.Debug => .debug,
else => .info,
},
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var server = try httpz.Server().init(allocator, .{ .port = PORT });
defer server.stop();
defer server.deinit();
var router = server.router();
router.get("/query_dispatch", dispatch.onQueryDispatch);
router.get("/query_gateway", dispatch.onQueryGateway);
router.post("/account/risky/api/check", authentication.onRiskyApiCheck);
router.post("/:product_name/mdk/shield/api/login", authentication.onShieldLogin);
router.post("/:product_name/mdk/shield/api/verify", authentication.onVerifyLogin);
router.post("/:product_name/combo/granter/login/v2/login", authentication.onComboTokenReq);
std.log.info("Dispatch is listening at localhost:{?}", .{server.config.port});
try server.listen();
}

28
gameserver/build.zig Normal file
View file

@ -0,0 +1,28 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const protocol = b.dependency("protocol", .{});
const exe = b.addExecutable(.{
.name = "gameserver",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("protocol", protocol.module("protocol"));
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

18
gameserver/build.zig.zon Normal file
View file

@ -0,0 +1,18 @@
.{
.name = "gameserver",
.version = "0.0.0",
.minimum_zig_version = "0.13.0",
.dependencies = .{
.protocol = .{
.path = "../protocol",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

77
gameserver/src/Packet.zig Normal file
View file

@ -0,0 +1,77 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Reader = std.net.Stream.Reader;
const Self = @This();
cmd_id: u16,
head: []u8,
body: []u8,
allocator: Allocator,
const head_magic: u32 = 0x9D74C714;
const tail_magic: u32 = 0xD7A152C8;
pub const DecodeError = error{
HeadMagicMismatch,
TailMagicMismatch,
PayloadTooBig,
};
pub fn read(reader: *Reader, allocator: Allocator) !Self {
if (try reader.readInt(u32, .big) != Self.head_magic) {
return Self.DecodeError.HeadMagicMismatch;
}
const cmd_id = try reader.readInt(u16, .big);
const head_len = try reader.readInt(u16, .big);
const body_len = try reader.readInt(u32, .big);
if (body_len > 0xFFFFFF) {
return Self.DecodeError.PayloadTooBig;
}
const head = try allocator.alloc(u8, head_len);
errdefer allocator.free(head);
const body = try allocator.alloc(u8, body_len);
errdefer allocator.free(body);
_ = try reader.readAll(head);
_ = try reader.readAll(body);
if (try reader.readInt(u32, .big) != Self.tail_magic) {
return Self.DecodeError.TailMagicMismatch;
}
return .{
.cmd_id = cmd_id,
.head = head,
.body = body,
.allocator = allocator,
};
}
pub fn getProto(self: *const Self, comptime T: type, allocator: Allocator) !T {
return try T.decode(self.body, allocator);
}
pub fn encode(cmd_id: u16, head: []u8, body: []u8, allocator: Allocator) ![]u8 {
var buf = try allocator.alloc(u8, 16 + head.len + body.len);
std.mem.writeInt(u32, buf[0..4], Self.head_magic, .big);
std.mem.writeInt(u16, buf[4..6], cmd_id, .big);
std.mem.writeInt(u16, buf[6..8], @intCast(head.len), .big);
std.mem.writeInt(u32, buf[8..12], @intCast(body.len), .big);
@memcpy(buf[12..(12 + head.len)], head);
@memcpy(buf[(12 + head.len)..(12 + head.len + body.len)], body);
std.mem.writeInt(u32, buf[(12 + head.len + body.len)..][0..4], Self.tail_magic, .big);
return buf;
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.head);
self.allocator.free(self.body);
}

View file

@ -0,0 +1,55 @@
const std = @import("std");
const protocol = @import("protocol");
const handlers = @import("handlers.zig");
const Packet = @import("Packet.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const Stream = std.net.Stream;
const Address = std.net.Address;
const Self = @This();
const log = std.log.scoped(.session);
address: Address,
stream: Stream,
allocator: Allocator,
pub fn init(address: Address, stream: Stream, allocator: Allocator) Self {
return .{
.address = address,
.stream = stream,
.allocator = allocator,
};
}
pub fn run(self: *Self) !void {
defer self.stream.close();
var reader = self.stream.reader();
while (true) {
var packet = Packet.read(&reader, self.allocator) catch break;
defer packet.deinit();
try handlers.handle(self, &packet);
}
}
pub fn send(self: *Self, cmd_id: protocol.CmdID, proto: anytype) !void {
const data = try proto.encode(self.allocator);
defer self.allocator.free(data);
const packet = try Packet.encode(@intFromEnum(cmd_id), &.{}, data, self.allocator);
defer self.allocator.free(packet);
_ = try self.stream.write(packet);
}
pub fn send_empty(self: *Self, cmd_id: protocol.CmdID) !void {
const packet = try Packet.encode(@intFromEnum(cmd_id), &.{}, &.{}, self.allocator);
defer self.allocator.free(packet);
_ = try self.stream.write(packet);
log.debug("sent EMPTY packet with id {}", .{cmd_id});
}

View file

@ -0,0 +1,71 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("Session.zig");
const Packet = @import("Packet.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const CmdID = protocol.CmdID;
const value_command = @import("./commands/value.zig");
const help_command = @import("./commands/help.zig");
const tp_command = @import("./commands/tp.zig");
const unstuck_command = @import("./commands/unstuck.zig");
const sync_command = @import("./commands/sync.zig");
const refill_command = @import("./commands/refill.zig");
// Add other errors if needed
const SystemErrors = error{ CommandError, SystemResources, Unexpected, AccessDenied, WouldBlock, ConnectionResetByPeer, OutOfMemory, DiskQuota, FileTooBig, InputOutput, NoSpaceLeft, DeviceBusy, InvalidArgument, BrokenPipe, OperationAborted };
const FileErrors = error{ NotOpenForWriting, LockViolation, Overflow, InvalidCharacter, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, SymLinkLoop, NameTooLong, FileNotFound, NotDir, NoDevice, SharingViolation, PathAlreadyExists, PipeBusy, InvalidUtf8, InvalidWtf8, BadPathName, NetworkNotFound, AntivirusInterference, IsDir, FileLocksNotSupported, FileBusy };
const NetworkErrors = error{ ConnectionTimedOut, NotOpenForReading, SocketNotConnected, Unseekable, StreamTooLong };
const ParseErrors = error{ UnexpectedToken, InvalidNumber, InvalidEnumTag, DuplicateField, UnknownField, MissingField, LengthMismatch, SyntaxError, UnexpectedEndOfInput, BufferUnderrun, ValueTooLong, InsufficientTokens, InvalidFormat };
const MiscErrors = error{ PermissionDenied, NetworkSubsystemFailed, FileSystem, CurrentWorkingDirectoryUnlinked, InvalidBatchScriptArg, InvalidExe, ResourceLimitReached, InvalidUserId, InvalidName, InvalidHandle, WaitAbandoned, WaitTimeOut, StdoutStreamTooLong, StderrStreamTooLong };
pub const Error = SystemErrors || FileErrors || NetworkErrors || ParseErrors || MiscErrors;
const CommandFn = *const fn (session: *Session, args: []const u8, allocator: Allocator) Error!void;
const Command = struct {
name: []const u8,
action: []const u8,
func: CommandFn,
};
const commandList = [_]Command{
Command{ .name = "help", .action = "", .func = help_command.handle },
Command{ .name = "test", .action = "", .func = value_command.handle },
Command{ .name = "node", .action = "", .func = value_command.challengeNode },
Command{ .name = "set", .action = "", .func = value_command.setGachaCommand },
Command{ .name = "tp", .action = "", .func = tp_command.handle },
Command{ .name = "unstuck", .action = "", .func = unstuck_command.handle },
Command{ .name = "sync", .action = "", .func = sync_command.onGenerateAndSync },
Command{ .name = "refill", .action = "", .func = refill_command.onRefill },
};
pub fn handleCommand(session: *Session, msg: []const u8, allocator: Allocator) Error!void {
if (msg.len < 1 or msg[0] != '/') {
std.debug.print("Message Text 2: {any}\n", .{msg});
return sendMessage(session, "Commands must start with a '/'", allocator);
}
const input = msg[1..]; // Remove the leading '/'
var tokenizer = std.mem.tokenize(u8, input, " ");
const command = tokenizer.next().?;
const args = tokenizer.rest();
for (commandList) |cmd| {
if (std.mem.eql(u8, cmd.name, command)) {
return try cmd.func(session, args, allocator);
}
}
try sendMessage(session, "Invalid command", allocator);
}
pub fn sendMessage(session: *Session, msg: []const u8, allocator: Allocator) Error!void {
var chat = protocol.RevcMsgScNotify.init(allocator);
chat.message_type = protocol.MsgType.MSG_TYPE_CUSTOM_TEXT;
chat.chat_type = protocol.ChatType.CHAT_TYPE_PRIVATE;
chat.source_uid = 2000;
chat.message_text = .{ .Const = msg };
chat.target_uid = 1; // receiver_id
try session.send(CmdID.CmdRevcMsgScNotify, chat);
}

View file

@ -0,0 +1,15 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const Allocator = std.mem.Allocator;
const Error = commandhandler.Error;
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) Error!void {
try commandhandler.sendMessage(session, "/tp to teleport, /sync to sync data from config\n", allocator);
try commandhandler.sendMessage(session, "/refill to refill technique point after battle\n", allocator);
try commandhandler.sendMessage(session, "/set to set gacha banner\n", allocator);
try commandhandler.sendMessage(session, "/node to chage node in PF, AS, MoC\n", allocator);
try commandhandler.sendMessage(session, "You can enter MoC, PF, AS via F4 menu\n", allocator);
try commandhandler.sendMessage(session, "(If your Castorice technique enabled, you must enter battle by using Castorice's technique)\n", allocator);
}

View file

@ -0,0 +1,22 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Data = @import("../data.zig");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const Error = commandhandler.Error;
pub fn onRefill(session: *Session, _: []const u8, allocator: Allocator) Error!void {
try commandhandler.sendMessage(session, "Refill skill point\n", allocator);
var sync = protocol.SyncLineupNotify.init(allocator);
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
sync.Lineup = lineup;
try session.send(CmdID.CmdSyncLineupNotify, sync);
}

View file

@ -0,0 +1,271 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Data = @import("../data.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const Error = commandhandler.Error;
// function to check the list if true
fn isInList(id: u32, list: []const u32) bool {
for (list) |item| {
if (item == id) {
return true;
}
}
return false;
}
fn syncItems(session: *Session, allocator: Allocator, equip_avatar: bool) !void {
var sync = protocol.PlayerSyncScNotify.init(allocator);
const config = try Config.loadGameConfig(allocator, "config.json");
for (config.avatar_config.items) |avatarConf| {
const equip_avatar_id: u32 = if (equip_avatar) avatarConf.id else 0;
const lc = protocol.Equipment{
.unique_id = if (equip_avatar) nextGlobalId(.Default) else nextGlobalId(.Undress),
.tid = avatarConf.lightcone.id,
.is_protected = true,
.level = avatarConf.lightcone.level,
.rank = avatarConf.lightcone.rank,
.promotion = avatarConf.lightcone.promotion,
.equip_avatar_id = equip_avatar_id,
};
try sync.equipment_list.append(lc);
for (avatarConf.relics.items) |input| {
var r = protocol.Relic{
.tid = input.id,
.main_affix_id = input.main_affix_id,
.unique_id = if (equip_avatar) nextGlobalId(.Default) else nextGlobalId(.Undress),
.exp = 0,
.equip_avatar_id = equip_avatar_id,
.is_protected = true,
.level = input.level,
.sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
.reforge_sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
};
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat1, .cnt = input.cnt1, .step = input.step1 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat2, .cnt = input.cnt2, .step = input.step2 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat3, .cnt = input.cnt3, .step = input.step3 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat4, .cnt = input.cnt4, .step = input.step4 });
try sync.relic_list.append(r);
}
}
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}
pub fn onSyncEquipment(session: *Session, _: []const u8, allocator: Allocator) Error!void {
try syncItems(session, allocator, false);
try syncItems(session, allocator, true);
}
pub fn onSyncAvatar(session: *Session, _: []const u8, allocator: Allocator) Error!void {
var sync = protocol.PlayerSyncScNotify.init(allocator);
const config = try Config.loadGameConfig(allocator, "config.json");
var char = protocol.AvatarSync.init(allocator);
for (Data.AllAvatars) |id| {
var avatar = protocol.Avatar.init(allocator);
avatar.base_avatar_id = id;
avatar.level = 80;
avatar.promotion = 6;
avatar.rank = 6;
avatar.has_taken_promotion_reward_list = ArrayList(u32).init(allocator);
for (1..6) |i| {
try avatar.has_taken_promotion_reward_list.append(@intCast(i));
}
var talentLevel: u32 = 0;
const skill_list: []const u32 = if (isInList(avatar.base_avatar_id, &Data.Rem)) &Data.skills else &Data.skills_old;
for (skill_list) |elem| {
talentLevel = switch (elem) {
1 => 6,
2...4 => 10,
301, 302 => if (isInList(avatar.base_avatar_id, &Data.Rem)) 6 else 1,
else => 1,
};
const talent = protocol.AvatarSkillTree{ .point_id = avatar.base_avatar_id * 1000 + elem, .level = talentLevel };
try avatar.skilltree_list.append(talent);
}
try char.avatar_list.append(avatar);
}
// rewrite data of avatar in config
for (config.avatar_config.items) |avatarConf| {
var avatar = protocol.Avatar.init(allocator);
// basic info
avatar.base_avatar_id = switch (avatarConf.id) {
8001...8008 => 8001,
1224 => 1001,
else => avatarConf.id,
};
avatar.level = avatarConf.level;
avatar.promotion = avatarConf.promotion;
avatar.rank = avatarConf.rank;
avatar.equipment_unique_id = nextGlobalId(.Avatar);
std.debug.print("LIGHTCONE SYNC {}\n", .{avatar.equipment_unique_id});
avatar.equip_relic_list = ArrayList(protocol.EquipRelic).init(allocator);
for (0..6) |i| {
try avatar.equip_relic_list.append(.{
.relic_unique_id = nextGlobalId(.Avatar), // uid
.type = @intCast(i), // slot
});
std.debug.print("EQUIPING SYNC {}:{}:{}\n", .{ avatarConf.id, avatar.equip_relic_list.items[i].relic_unique_id, i });
}
var talentLevel: u32 = 0;
const skill_list: []const u32 = if (isInList(avatar.base_avatar_id, &Data.Rem)) &Data.skills else &Data.skills_old;
for (skill_list) |elem| {
talentLevel = switch (elem) {
1 => 6,
2...4 => 10,
301, 302 => if (isInList(avatar.base_avatar_id, &Data.Rem)) 6 else 1,
else => 1,
};
const talent = protocol.AvatarSkillTree{ .point_id = avatar.base_avatar_id * 1000 + elem, .level = talentLevel };
try avatar.skilltree_list.append(talent);
}
try char.avatar_list.append(avatar);
const avatarType: protocol.MultiPathAvatarType = @enumFromInt(avatarConf.id);
if (@intFromEnum(avatarType) > 1) {
std.debug.print("setting avatar type: {}\n", .{avatarConf.id});
try session.send(CmdID.CmdSetAvatarPathScRsp, protocol.SetAvatarPathScRsp{
.retcode = 0,
.avatar_id = avatarType,
});
}
}
sync.avatar_sync = char;
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}
// TODO: DO WITH MALE MC TOO :Đ
pub fn onSyncMultiPath(session: *Session, _: []const u8, allocator: Allocator) Error!void {
var sync = protocol.PlayerSyncScNotify.init(allocator);
const config = try Config.loadGameConfig(allocator, "config.json");
const currentAvatarId = getCurrentGlobalId(.Avatar);
const GeneratorType = UidGen();
const avatar_ids = [_][]const u32{
&[_]u32{ 8001, 8002 },
&[_]u32{ 8003, 8004 },
&[_]u32{ 8005, 8006 },
&[_]u32{ 8007, 8008 },
&[_]u32{1001},
&[_]u32{1224},
};
const avatar_types = [_]protocol.MultiPathAvatarType{
.GirlWarriorType, .GirlKnightType, .GirlShamanType,
.GirlMemoryType, .Mar_7thKnightType, .Mar_7thRogueType,
};
var indexes: [6]u32 = [_]u32{0} ** 6;
var counts: [6]u32 = [_]u32{0} ** 6;
var multis: [6]protocol.MultiPathAvatarInfo = undefined;
for (&multis, avatar_types, 0..) |*multi, avatar_type, i| {
std.debug.print("MULTIPATH AVATAR INDEX: {} IS {}\n", .{ i, avatar_type });
multi.* = protocol.MultiPathAvatarInfo.init(allocator);
multi.avatar_id = avatar_type;
if (avatar_type == .Mar_7thKnightType) {
multi.dressed_skin_id = 1100101;
}
}
for (config.avatar_config.items) |avatar| {
for (0..avatar_ids.len) |i| {
counts[i] += 1;
for (avatar_ids[i]) |id| {
if (avatar.id == id) {
multis[i].rank = avatar.rank;
indexes[i] = 5 - counts[i];
}
}
}
}
var generators: [6]GeneratorType = undefined;
for (0..multis.len) |i| {
generators[i] = GeneratorType.init(currentAvatarId - (indexes[i] * 7) + 1);
}
for (0..multis.len) |i| {
var multi = &multis[i];
var gen = &generators[i];
multi.path_equipment_id = currentAvatarId - (indexes[i] * 7) + 1;
multi.equip_relic_list = ArrayList(protocol.EquipRelic).init(allocator);
for (0..6) |slot| {
try multi.equip_relic_list.append(.{
.relic_unique_id = gen.nextId(),
.type = @intCast(slot),
});
}
}
for (0..multis.len) |i| {
const skill_set = if (i == 3) &Data.skills else &Data.skills_old;
for (skill_set) |skill| {
const talent_level: u32 = if (skill == 1 or skill == 301 or skill == 302) 6 else if (skill <= 4) 10 else 1;
const point_id = if (avatar_ids[i].len > 1)
avatar_ids[i][1] * 1000 + skill
else
avatar_ids[i][0] * 1000 + skill;
const talent = protocol.AvatarSkillTree{
.point_id = point_id,
.level = talent_level,
};
try multis[i].multi_path_skill_tree.append(talent);
}
}
try sync.multi_path_avatar_info_list.appendSlice(&multis);
try session.send(CmdID.CmdPlayerSyncScNotify, sync);
}
pub const UidType = enum { Default, Undress, Avatar };
pub var global_uid_gen = UidGenerator.init(.Default, 28);
pub var global_uid_gen_undress = UidGenerator.init(.Undress, 0);
pub var global_uid_gen_avatar = UidGenerator.init(.Avatar, 28);
pub fn nextGlobalId(uid_type: UidType) u32 {
return switch (uid_type) {
.Default => global_uid_gen.nextId(),
.Undress => global_uid_gen_undress.nextId(),
.Avatar => global_uid_gen_avatar.nextId(),
};
}
pub fn getCurrentGlobalId(uid_type: UidType) u32 {
return switch (uid_type) {
.Default => global_uid_gen.getCurrentId(),
.Undress => global_uid_gen_undress.getCurrentId(),
.Avatar => global_uid_gen_avatar.getCurrentId(),
};
}
pub const UidGenerator = struct {
current_id: u32,
uid_type: UidType,
pub fn init(uid_type: UidType, start_id: u32) UidGenerator {
return UidGenerator{ .uid_type = uid_type, .current_id = start_id };
}
pub fn nextId(self: *UidGenerator) u32 {
self.current_id += 1;
return self.current_id;
}
pub fn getCurrentId(self: *const UidGenerator) u32 {
return self.current_id;
}
};
pub fn UidGen() type {
return struct {
current_id: u32,
const Self = @This();
pub fn init(initial_id: u32) Self {
return Self{ .current_id = initial_id };
}
pub fn nextId(self: *Self) u32 {
self.current_id +%= 1;
return self.current_id;
}
};
}
pub fn onGenerateAndSync(session: *Session, placeholder: []const u8, allocator: Allocator) Error!void {
try commandhandler.sendMessage(session, "Sync items with config\n", allocator);
try onSyncEquipment(session, placeholder, allocator);
try onSyncAvatar(session, placeholder, allocator);
try onSyncMultiPath(session, placeholder, allocator);
}

View file

@ -0,0 +1,61 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const Config = @import("../services/config.zig");
const Res_config = @import("../services/res_config.zig");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const SceneManager = @import("../manager/scene_mgr.zig").SceneManager;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const Error = commandhandler.Error;
pub fn handle(session: *Session, args: []const u8, allocator: Allocator) Error!void {
var arg_iter = std.mem.split(u8, args, " ");
const entry_id_str = arg_iter.next() orelse {
try commandhandler.sendMessage(session, "Error: Missing arguments.\nUsage: /tp <entry_id> [plane_id] [floor_id]", allocator);
return;
};
const entry_id = std.fmt.parseInt(u32, entry_id_str, 10) catch {
try commandhandler.sendMessage(session, "Error: Invalid entry ID. Please provide a valid unsigned 32-bit integer.", allocator);
return;
};
var plane_id: ?u32 = null;
if (arg_iter.next()) |plane_id_str| {
plane_id = std.fmt.parseInt(u32, plane_id_str, 10) catch {
try commandhandler.sendMessage(session, "Error: Invalid plane ID. Please provide a valid unsigned 32-bit integer.", allocator);
return;
};
}
var floor_id: ?u32 = null;
if (arg_iter.next()) |floor_id_str| {
floor_id = std.fmt.parseInt(u32, floor_id_str, 10) catch {
try commandhandler.sendMessage(session, "Error: Invalid floor ID. Please provide a valid unsigned 32-bit integer.", allocator);
return;
};
}
var tp_msg = try std.fmt.allocPrint(allocator, "Teleporting to entry ID: {d}", .{entry_id});
if (plane_id) |pid| {
tp_msg = try std.fmt.allocPrint(allocator, "{s}, plane ID: {d}", .{ tp_msg, pid });
}
if (floor_id) |fid| {
tp_msg = try std.fmt.allocPrint(allocator, "{s}, floor ID: {d}", .{ tp_msg, fid });
}
try commandhandler.sendMessage(session, std.fmt.allocPrint(allocator, "Teleporting to entry ID: {d} {any} {any}\n", .{ entry_id, plane_id, floor_id }) catch "Error formatting message", allocator);
var planeID: u32 = 0;
var floorID: u32 = 0;
if (plane_id) |pid| planeID = pid;
if (floor_id) |fid| floorID = fid;
var scene_manager = SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(planeID, floorID, entry_id, 0);
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.lineup = lineup,
.scene = scene_info,
});
}

View file

@ -0,0 +1,23 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const protocol = @import("protocol");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const SceneManager = @import("../manager/scene_mgr.zig").SceneManager;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const Error = commandhandler.Error;
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) Error!void {
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
var scene_manager = SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(20421, 20421001, 2042101, 2042106);
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_DIMENSION_MERGE,
.lineup = lineup,
.scene = scene_info,
});
}

View file

@ -0,0 +1,124 @@
const commandhandler = @import("../command.zig");
const std = @import("std");
const Session = @import("../Session.zig");
const Allocator = std.mem.Allocator;
const Error = commandhandler.Error;
pub var challenge_node: u32 = 0;
pub var StandardBanner = [_]u32{ 1003, 1004, 1101, 1104, 1209, 1211 };
pub var RateUp = [_]u32{1406};
pub var RateUpFourStars = [_]u32{ 1210, 1108, 1207 };
pub fn handle(session: *Session, _: []const u8, allocator: Allocator) Error!void {
try commandhandler.sendMessage(session, "Test Command for Chat\n", allocator);
}
pub fn challengeNode(session: *Session, _: []const u8, allocator: Allocator) Error!void {
if (challenge_node == 0) {
try commandhandler.sendMessage(session, "Change Challenge Node 2 \n", allocator);
challenge_node = challenge_node + 1;
} else {
try commandhandler.sendMessage(session, "Change Challenge Node 1 \n", allocator);
challenge_node = challenge_node - 1;
}
}
pub fn setGachaCommand(session: *Session, args: []const u8, allocator: Allocator) Error!void {
var arg_iter = std.mem.split(u8, args, " ");
const command = arg_iter.next() orelse {
try commandhandler.sendMessage(session, "Error: Missing sub-command. Usage: /set <sub-command> [arguments]", allocator);
return;
};
if (std.mem.eql(u8, command, "standard")) {
try standard(session, &arg_iter, allocator);
} else if (std.mem.eql(u8, command, "rateup")) {
const next = arg_iter.next();
if (next) |rateup_number| {
if (std.mem.eql(u8, rateup_number, "5")) {
try gacha5Stars(session, &arg_iter, allocator);
} else if (std.mem.eql(u8, rateup_number, "4")) {
try gacha4Stars(session, &arg_iter, allocator);
} else {
try commandhandler.sendMessage(session, "Error: Invalid rateup number. Please use 4 (four stars) or 5 (5 stars).", allocator);
}
} else {
try commandhandler.sendMessage(session, "Error: Missing number for rateup. Usage: /set rateup <number>", allocator);
}
} else {
try commandhandler.sendMessage(session, "Error: Unknown sub-command. Available: standard, rateup 5, rateup 4", allocator);
}
}
fn standard(session: *Session, arg_iter: *std.mem.SplitIterator(u8, .sequence), allocator: Allocator) Error!void {
var avatar_ids: [6]u32 = undefined;
var count: usize = 0;
while (count < 6) {
if (arg_iter.next()) |avatar_id_str| {
const id = std.fmt.parseInt(u32, avatar_id_str, 10) catch {
return sendErrorMessage(session, "Error: Invalid avatar ID. Please provide a valid unsigned 32-bit integer.", allocator);
};
if (!isValidAvatarId(id)) {
return sendErrorMessage(session, "Error: Invalid Avatar ID format.", allocator);
}
avatar_ids[count] = id;
count += 1;
} else {
break;
}
}
if (arg_iter.next() != null or count != 6) {
return sendErrorMessage(session, "Error: You must provide exactly 6 avatar IDs.", allocator);
}
@memcpy(&StandardBanner, &avatar_ids);
const msg = try std.fmt.allocPrint(allocator, "Set standard banner ID to: {d}, {d}, {d}, {d}, {d}, {d}", .{ avatar_ids[0], avatar_ids[1], avatar_ids[2], avatar_ids[3], avatar_ids[4], avatar_ids[5] });
try commandhandler.sendMessage(session, msg, allocator);
}
fn gacha4Stars(session: *Session, arg_iter: *std.mem.SplitIterator(u8, .sequence), allocator: Allocator) Error!void {
var avatar_ids: [3]u32 = undefined;
var count: usize = 0;
while (count < 3) {
if (arg_iter.next()) |avatar_id_str| {
const id = std.fmt.parseInt(u32, avatar_id_str, 10) catch {
return sendErrorMessage(session, "Error: Invalid avatar ID. Please provide a valid unsigned 32-bit integer.", allocator);
};
if (!isValidAvatarId(id)) {
return sendErrorMessage(session, "Error: Invalid Avatar ID format.", allocator);
}
avatar_ids[count] = id;
count += 1;
} else {
break;
}
}
if (arg_iter.next() != null or count != 3) {
return sendErrorMessage(session, "Error: You must provide exactly 3 avatar IDs.", allocator);
}
@memcpy(&RateUpFourStars, &avatar_ids);
const msg = try std.fmt.allocPrint(allocator, "Set 4 star rate up ID to: {d}, {d}, {d}", .{ avatar_ids[0], avatar_ids[1], avatar_ids[2] });
try commandhandler.sendMessage(session, msg, allocator);
}
fn gacha5Stars(session: *Session, arg_iter: *std.mem.SplitIterator(u8, .sequence), allocator: Allocator) Error!void {
var avatar_ids: [1]u32 = undefined;
if (arg_iter.next()) |avatar_id_str| {
const id = std.fmt.parseInt(u32, avatar_id_str, 10) catch {
return sendErrorMessage(session, "Error: Invalid avatar ID. Please provide a valid unsigned 32-bit integer.", allocator);
};
if (!isValidAvatarId(id)) {
return sendErrorMessage(session, "Error: Invalid Avatar ID format.", allocator);
}
avatar_ids[0] = id;
} else {
return sendErrorMessage(session, "Error: You must provide a rate-up avatar ID.", allocator);
}
if (arg_iter.next() != null) {
return sendErrorMessage(session, "Error: Only one rate-up avatar ID is allowed.", allocator);
}
@memcpy(&RateUp, &avatar_ids);
const msg = try std.fmt.allocPrint(allocator, "Set rate up ID to: {d}", .{avatar_ids[0]});
try commandhandler.sendMessage(session, msg, allocator);
}
fn sendErrorMessage(session: *Session, message: []const u8, allocator: Allocator) Error!void {
try commandhandler.sendMessage(session, message, allocator);
}
fn isValidAvatarId(avatar_id: u32) bool {
return avatar_id >= 1000 and avatar_id <= 9999;
}

257
gameserver/src/data.zig Normal file

File diff suppressed because one or more lines are too long

251
gameserver/src/handlers.zig Normal file
View file

@ -0,0 +1,251 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("Session.zig");
const Packet = @import("Packet.zig");
const avatar = @import("services/avatar.zig");
const chat = @import("services/chat.zig");
const gacha = @import("services/gacha.zig");
const item = @import("services/item.zig");
const battle = @import("services/battle.zig");
const login = @import("services/login.zig");
const lineup = @import("services/lineup.zig");
const mail = @import("services/mail.zig");
const misc = @import("services/misc.zig");
const mission = @import("services/mission.zig");
const pet = @import("services/pet.zig");
const profile = @import("services/profile.zig");
const scene = @import("services/scene.zig");
const events = @import("services/events.zig");
const challenge = @import("services/challenge.zig");
const multipath = @import("services/multipath.zig");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const CmdID = protocol.CmdID;
const log = std.log.scoped(.handlers);
const Action = *const fn (*Session, *const Packet, Allocator) anyerror!void;
const HandlerList = [_]struct { CmdID, Action }{
.{ CmdID.CmdPlayerGetTokenCsReq, login.onPlayerGetToken },
.{ CmdID.CmdPlayerLoginCsReq, login.onPlayerLogin },
.{ CmdID.CmdPlayerHeartBeatCsReq, misc.onPlayerHeartBeat },
.{ CmdID.CmdPlayerLoginFinishCsReq, login.onPlayerLoginFinish },
.{ CmdID.CmdContentPackageGetDataCsReq, login.onContentPackageGetData },
.{ CmdID.CmdSetClientPausedCsReq, login.onSetClientPaused },
//avatar
.{ CmdID.CmdGetAvatarDataCsReq, avatar.onGetAvatarData },
.{ CmdID.CmdSetAvatarPathCsReq, avatar.onSetAvatarPath },
.{ CmdID.CmdGetBasicInfoCsReq, avatar.onGetBasicInfo },
.{ CmdID.CmdGetMultiPathAvatarInfoCsReq, multipath.onGetMultiPathAvatarInfo },
.{ CmdID.CmdTakeOffAvatarSkinCsReq, avatar.onTakeOffAvatarSkin },
.{ CmdID.CmdDressAvatarSkinCsReq, avatar.onDressAvatarSkin },
.{ CmdID.CmdGetBigDataAllRecommendCsReq, avatar.onGetBigDataAll },
.{ CmdID.CmdGetBigDataRecommendCsReq, avatar.onGetBigData },
//bag
.{ CmdID.CmdGetBagCsReq, item.onGetBag },
.{ CmdID.CmdUseItemCsReq, item.onUseItem },
//lineup
.{ CmdID.CmdChangeLineupLeaderCsReq, lineup.onChangeLineupLeader },
.{ CmdID.CmdReplaceLineupCsReq, lineup.onReplaceLineup },
.{ CmdID.CmdGetCurLineupDataCsReq, lineup.onGetCurLineupData },
//battle
.{ CmdID.CmdStartCocoonStageCsReq, battle.onStartCocoonStage },
.{ CmdID.CmdPVEBattleResultCsReq, battle.onPVEBattleResult },
.{ CmdID.CmdSceneCastSkillCsReq, battle.onSceneCastSkill },
.{ CmdID.CmdSceneCastSkillCostMpCsReq, battle.onSceneCastSkillCostMp },
.{ CmdID.CmdQuickStartCocoonStageCsReq, battle.onQuickStartCocoonStage },
.{ CmdID.CmdQuickStartFarmElementCsReq, battle.onQuickStartFarmElement },
.{ CmdID.CmdStartBattleCollegeCsReq, battle.onStartBattleCollege },
//gacha
.{ CmdID.CmdGetGachaInfoCsReq, gacha.onGetGachaInfo },
.{ CmdID.CmdBuyGoodsCsReq, gacha.onBuyGoods },
.{ CmdID.CmdGetShopListCsReq, gacha.onGetShopList },
.{ CmdID.CmdExchangeHcoinCsReq, gacha.onExchangeHcoin },
.{ CmdID.CmdDoGachaCsReq, gacha.onDoGacha },
//mail
.{ CmdID.CmdGetMailCsReq, mail.onGetMail },
//pet
.{ CmdID.CmdGetPetDataCsReq, pet.onGetPetData },
.{ CmdID.CmdRecallPetCsReq, pet.onRecallPet },
.{ CmdID.CmdSummonPetCsReq, pet.onSummonPet },
//profile
.{ CmdID.CmdGetPhoneDataCsReq, profile.onGetPhoneData },
.{ CmdID.CmdSelectPhoneThemeCsReq, profile.onSelectPhoneTheme },
.{ CmdID.CmdSelectChatBubbleCsReq, profile.onSelectChatBubble },
.{ CmdID.CmdGetPlayerBoardDataCsReq, profile.onGetPlayerBoardData },
.{ CmdID.CmdSetDisplayAvatarCsReq, profile.onSetDisplayAvatar },
.{ CmdID.CmdSetAssistAvatarCsReq, profile.onSetAssistAvatar },
.{ CmdID.CmdSetSignatureCsReq, profile.onSetSignature },
.{ CmdID.CmdSetGameplayBirthdayCsReq, profile.onSetGameplayBirthday },
.{ CmdID.CmdSetHeadIconCsReq, profile.onSetHeadIcon },
//mission
.{ CmdID.CmdGetTutorialGuideCsReq, mission.onGetTutorialGuideStatus },
.{ CmdID.CmdGetMissionStatusCsReq, mission.onGetMissionStatus },
.{ CmdID.CmdGetTutorialCsReq, mission.onGetTutorialStatus },
.{ CmdID.CmdFinishTutorialGuideCsReq, mission.onFinishTutorialGuideStatus },
.{ CmdID.CmdUnlockTutorialGuideCsReq, mission.onUnlockTutorialGuide },
//chat
.{ CmdID.CmdGetFriendListInfoCsReq, chat.onGetFriendListInfo },
.{ CmdID.CmdGetPrivateChatHistoryCsReq, chat.onPrivateChatHistory },
.{ CmdID.CmdGetChatEmojiListCsReq, chat.onChatEmojiList },
.{ CmdID.CmdSendMsgCsReq, chat.onSendMsg },
//scene
.{ CmdID.CmdGetCurSceneInfoCsReq, scene.onGetCurSceneInfo },
.{ CmdID.CmdSceneEntityMoveCsReq, scene.onSceneEntityMove },
.{ CmdID.CmdEnterSceneCsReq, scene.onEnterScene },
.{ CmdID.CmdGetSceneMapInfoCsReq, scene.onGetSceneMapInfo },
.{ CmdID.CmdGetUnlockTeleportCsReq, scene.onGetUnlockTeleport },
.{ CmdID.CmdEnterSectionCsReq, scene.onEnterSection },
.{ CmdID.CmdSceneEntityTeleportCsReq, scene.onSceneEntityTeleport },
.{ CmdID.CmdGetFirstTalkNpcCsReq, scene.onGetFirstTalkNpc },
.{ CmdID.CmdGetFirstTalkByPerformanceNpcCsReq, scene.onGetFirstTalkByPerformanceNp },
.{ CmdID.CmdGetNpcTakenRewardCsReq, scene.onGetNpcTakenReward },
.{ CmdID.CmdUpdateGroupPropertyCsReq, scene.onUpdateGroupProperty },
.{ CmdID.CmdChangePropTimelineInfoCsReq, scene.onChangePropTimeline },
.{ CmdID.CmdDeactivateFarmElementCsReq, scene.onDeactivateFarmElement },
.{ CmdID.CmdSetGroupCustomSaveDataCsReq, scene.onSetGroupCustomSaveData },
//events
.{ CmdID.CmdGetActivityScheduleConfigCsReq, events.onGetActivity },
//challenge
.{ CmdID.CmdGetChallengeCsReq, challenge.onGetChallenge },
.{ CmdID.CmdGetChallengeGroupStatisticsCsReq, challenge.onGetChallengeGroupStatistics },
.{ CmdID.CmdStartChallengeCsReq, challenge.onStartChallenge },
.{ CmdID.CmdLeaveChallengeCsReq, challenge.onLeaveChallenge },
};
const DummyCmdList = [_]struct { CmdID, CmdID }{
.{ CmdID.CmdGetBagCsReq, CmdID.CmdGetBagScRsp },
.{ CmdID.CmdGetMarkItemListCsReq, CmdID.CmdGetMarkItemListScRsp },
.{ CmdID.CmdGetPlayerBoardDataCsReq, CmdID.CmdGetPlayerBoardDataScRsp },
.{ CmdID.CmdGetCurAssistCsReq, CmdID.CmdGetCurAssistScRsp },
.{ CmdID.CmdGetAllLineupDataCsReq, CmdID.CmdGetAllLineupDataScRsp },
.{ CmdID.CmdGetAllServerPrefsDataCsReq, CmdID.CmdGetAllServerPrefsDataScRsp },
.{ CmdID.CmdGetMissionDataCsReq, CmdID.CmdGetMissionDataScRsp },
.{ CmdID.CmdGetQuestDataCsReq, CmdID.CmdGetQuestDataScRsp },
.{ CmdID.CmdGetCurChallengeCsReq, CmdID.CmdGetCurChallengeScRsp },
.{ CmdID.CmdGetRogueCommonDialogueDataCsReq, CmdID.CmdGetRogueCommonDialogueDataScRsp },
.{ CmdID.CmdGetRogueInfoCsReq, CmdID.CmdGetRogueInfoScRsp },
.{ CmdID.CmdGetRogueHandbookDataCsReq, CmdID.CmdGetRogueHandbookDataScRsp },
.{ CmdID.CmdGetRogueEndlessActivityDataCsReq, CmdID.CmdGetRogueEndlessActivityDataScRsp },
.{ CmdID.CmdChessRogueQueryCsReq, CmdID.CmdChessRogueQueryScRsp },
.{ CmdID.CmdRogueTournQueryCsReq, CmdID.CmdRogueTournQueryScRsp },
.{ CmdID.CmdSyncClientResVersionCsReq, CmdID.CmdSyncClientResVersionScRsp },
.{ CmdID.CmdDailyFirstMeetPamCsReq, CmdID.CmdDailyFirstMeetPamScRsp },
.{ CmdID.CmdGetBattleCollegeDataCsReq, CmdID.CmdGetBattleCollegeDataScRsp },
.{ CmdID.CmdGetNpcStatusCsReq, CmdID.CmdGetNpcStatusScRsp },
.{ CmdID.CmdGetSecretKeyInfoCsReq, CmdID.CmdGetSecretKeyInfoScRsp },
.{ CmdID.CmdGetHeartDialInfoCsReq, CmdID.CmdGetHeartDialInfoScRsp },
.{ CmdID.CmdGetVideoVersionKeyCsReq, CmdID.CmdGetVideoVersionKeyScRsp },
.{ CmdID.CmdGetCurBattleInfoCsReq, CmdID.CmdGetCurBattleInfoScRsp },
.{ CmdID.CmdHeliobusActivityDataCsReq, CmdID.CmdHeliobusActivityDataScRsp },
.{ CmdID.CmdGetAetherDivideInfoCsReq, CmdID.CmdGetAetherDivideInfoScRsp },
.{ CmdID.CmdGetMapRotationDataCsReq, CmdID.CmdGetMapRotationDataScRsp },
.{ CmdID.CmdGetRogueCollectionCsReq, CmdID.CmdGetRogueCollectionScRsp },
.{ CmdID.CmdGetRogueExhibitionCsReq, CmdID.CmdGetRogueExhibitionScRsp },
.{ CmdID.CmdPlayerReturnInfoQueryCsReq, CmdID.CmdPlayerReturnInfoQueryScRsp },
.{ CmdID.CmdGetLevelRewardTakenListCsReq, CmdID.CmdGetLevelRewardTakenListScRsp },
.{ CmdID.CmdGetMainMissionCustomValueCsReq, CmdID.CmdGetMainMissionCustomValueScRsp },
.{ CmdID.CmdGetMaterialSubmitActivityDataCsReq, CmdID.CmdGetMaterialSubmitActivityDataScRsp },
.{ CmdID.CmdRogueTournGetCurRogueCocoonInfoCsReq, CmdID.CmdRogueTournGetCurRogueCocoonInfoScRsp },
.{ CmdID.CmdRogueMagicQueryCsReq, CmdID.CmdRogueMagicQueryScRsp },
.{ CmdID.CmdMusicRhythmDataCsReq, CmdID.CmdMusicRhythmDataScRsp },
//friendlist
.{ CmdID.CmdGetFriendApplyListInfoCsReq, CmdID.CmdGetFriendApplyListInfoScRsp },
.{ CmdID.CmdGetChatFriendHistoryCsReq, CmdID.CmdGetChatFriendHistoryScRsp },
.{ CmdID.CmdGetFriendLoginInfoCsReq, CmdID.CmdGetFriendLoginInfoScRsp },
.{ CmdID.CmdGetFriendBattleRecordDetailCsReq, CmdID.CmdGetFriendBattleRecordDetailScRsp },
.{ CmdID.CmdGetFriendDevelopmentInfoCsReq, CmdID.CmdGetFriendDevelopmentInfoScRsp },
.{ CmdID.CmdGetFriendRecommendListInfoCsReq, CmdID.CmdGetFriendRecommendListInfoScRsp },
//add
.{ CmdID.CmdSwitchHandDataCsReq, CmdID.CmdSwitchHandDataScRsp },
.{ CmdID.CmdRogueArcadeGetInfoCsReq, CmdID.CmdRogueArcadeGetInfoScRsp },
.{ CmdID.CmdGetMissionMessageInfoCsReq, CmdID.CmdGetMissionMessageInfoScRsp },
.{ CmdID.CmdTrainPartyGetDataCsReq, CmdID.CmdTrainPartyGetDataScRsp },
.{ CmdID.CmdGetEnteredSceneCsReq, CmdID.CmdGetEnteredSceneScRsp },
.{ CmdID.CmdQueryProductInfoCsReq, CmdID.CmdQueryProductInfoScRsp },
.{ CmdID.CmdGetPamSkinDataCsReq, CmdID.CmdGetPamSkinDataScRsp },
.{ CmdID.CmdGetRogueScoreRewardInfoCsReq, CmdID.CmdGetRogueScoreRewardInfoScRsp },
.{ CmdID.CmdGetQuestRecordCsReq, CmdID.CmdGetQuestRecordScRsp },
.{ CmdID.CmdGetDailyActiveInfoCsReq, CmdID.CmdGetDailyActiveInfoScRsp },
.{ CmdID.CmdGetChessRogueNousStoryInfoCsReq, CmdID.CmdGetChessRogueNousStoryInfoScRsp },
.{ CmdID.CmdCommonRogueQueryCsReq, CmdID.CmdCommonRogueQueryScRsp },
.{ CmdID.CmdGetFightActivityDataCsReq, CmdID.CmdGetFightActivityDataScRsp },
.{ CmdID.CmdGetStarFightDataCsReq, CmdID.CmdGetStarFightDataScRsp },
.{ CmdID.CmdGetMultipleDropInfoCsReq, CmdID.CmdGetMultipleDropInfoScRsp },
.{ CmdID.CmdGetPlayerReturnMultiDropInfoCsReq, CmdID.CmdGetPlayerReturnMultiDropInfoScRsp },
.{ CmdID.CmdGetShareDataCsReq, CmdID.CmdGetShareDataScRsp },
.{ CmdID.CmdGetTreasureDungeonActivityDataCsReq, CmdID.CmdGetTreasureDungeonActivityDataScRsp },
.{ CmdID.CmdEvolveBuildQueryInfoCsReq, CmdID.CmdEvolveBuildQueryInfoScRsp },
.{ CmdID.CmdGetAlleyInfoCsReq, CmdID.CmdGetAlleyInfoScRsp },
.{ CmdID.CmdGetAetherDivideChallengeInfoCsReq, CmdID.CmdGetAetherDivideChallengeInfoScRsp },
.{ CmdID.CmdGetStrongChallengeActivityDataCsReq, CmdID.CmdGetStrongChallengeActivityDataScRsp },
.{ CmdID.CmdGetOfferingInfoCsReq, CmdID.CmdGetOfferingInfoScRsp },
.{ CmdID.CmdClockParkGetInfoCsReq, CmdID.CmdClockParkGetInfoScRsp },
.{ CmdID.CmdGetGunPlayDataCsReq, CmdID.CmdGetGunPlayDataScRsp },
.{ CmdID.CmdGetTrackPhotoActivityDataCsReq, CmdID.CmdGetTrackPhotoActivityDataScRsp },
.{ CmdID.CmdGetSwordTrainingDataCsReq, CmdID.CmdGetSwordTrainingDataScRsp },
.{ CmdID.CmdGetFightFestDataCsReq, CmdID.CmdGetFightFestDataScRsp },
.{ CmdID.CmdDifficultyAdjustmentGetDataCsReq, CmdID.CmdDifficultyAdjustmentGetDataScRsp },
.{ CmdID.CmdSpaceZooDataCsReq, CmdID.CmdSpaceZooDataScRsp },
.{ CmdID.CmdGetExpeditionDataCsReq, CmdID.CmdGetExpeditionDataScRsp },
.{ CmdID.CmdTravelBrochureGetDataCsReq, CmdID.CmdTravelBrochureGetDataScRsp },
.{ CmdID.CmdRaidCollectionDataCsReq, CmdID.CmdRaidCollectionDataScRsp },
.{ CmdID.CmdGetRaidInfoCsReq, CmdID.CmdGetRaidInfoScRsp },
.{ CmdID.CmdGetLoginActivityCsReq, CmdID.CmdGetLoginActivityScRsp },
.{ CmdID.CmdGetTrialActivityDataCsReq, CmdID.CmdGetTrialActivityDataScRsp },
.{ CmdID.CmdGetJukeboxDataCsReq, CmdID.CmdGetJukeboxDataScRsp },
.{ CmdID.CmdGetMuseumInfoCsReq, CmdID.CmdGetMuseumInfoScRsp },
.{ CmdID.CmdGetTelevisionActivityDataCsReq, CmdID.CmdGetTelevisionActivityDataScRsp },
.{ CmdID.CmdGetTrainVisitorRegisterCsReq, CmdID.CmdGetTrainVisitorRegisterScRsp },
.{ CmdID.CmdGetBoxingClubInfoCsReq, CmdID.CmdGetBoxingClubInfoScRsp },
.{ CmdID.CmdTextJoinQueryCsReq, CmdID.CmdTextJoinQueryScRsp },
.{ CmdID.CmdGetLoginChatInfoCsReq, CmdID.CmdGetLoginChatInfoScRsp },
.{ CmdID.CmdGetFeverTimeActivityDataCsReq, CmdID.CmdGetFeverTimeActivityDataScRsp },
.{ CmdID.CmdGetSummonActivityDataCsReq, CmdID.CmdGetSummonActivityDataScRsp },
.{ CmdID.CmdTarotBookGetDataCsReq, CmdID.CmdTarotBookGetDataScRsp },
.{ CmdID.CmdGetMarkChestCsReq, CmdID.CmdGetMarkChestScRsp },
.{ CmdID.CmdMatchThreeGetDataCsReq, CmdID.CmdMatchThreeGetDataScRsp },
.{ CmdID.CmdUpdateServerPrefsDataCsReq, CmdID.CmdUpdateServerPrefsDataScRsp },
.{ CmdID.CmdUpdateTrackMainMissionIdCsReq, CmdID.CmdUpdateTrackMainMissionIdScRsp },
.{ CmdID.CmdGetNpcMessageGroupCsReq, CmdID.CmdGetNpcMessageGroupScRsp },
.{ CmdID.CmdGetAllSaveRaidCsReq, CmdID.CmdGetAllSaveRaidScRsp },
.{ CmdID.CmdGetAssistHistoryCsReq, CmdID.CmdGetAssistHistoryScRsp },
.{ CmdID.CmdGetPlayerDetailInfoCsReq, CmdID.CmdGetPlayerDetailInfoScRsp },
.{ CmdID.CmdGetEraFlipperDataCsReq, CmdID.CmdGetEraFlipperDataScRsp },
.{ CmdID.CmdGetPreAvatarListCsReq, CmdID.CmdGetPreAvatarListScRsp },
.{ CmdID.CmdGetRechargeGiftInfoCsReq, CmdID.CmdGetRechargeGiftInfoScRsp },
.{ CmdID.CmdGetRechargeBenefitInfoCsReq, CmdID.CmdGetRechargeBenefitInfoScRsp },
.{ CmdID.CmdRelicSmartWearGetPlanCsReq, CmdID.CmdRelicSmartWearGetPlanScRsp },
.{ CmdID.CmdRelicSmartWearGetPinRelicCsReq, CmdID.CmdRelicSmartWearGetPinRelicScRsp },
.{ CmdID.CmdSetGrowthTargetAvatarCsReq, CmdID.CmdSetGrowthTargetAvatarScRsp },
//.{ CmdID.CmdGetStoryTokenActivityDataCsReq, CmdID.CmdGetStoryTokenActivityDataScRsp },
};
const SuppressLogList = [_]CmdID{CmdID.CmdSceneEntityMoveCsReq};
pub fn handle(session: *Session, packet: *const Packet) !void {
var arena = ArenaAllocator.init(session.allocator);
defer arena.deinit();
const cmd_id: CmdID = @enumFromInt(packet.cmd_id);
inline for (HandlerList) |handler| {
if (handler[0] == cmd_id) {
try handler[1](session, packet, arena.allocator());
if (!std.mem.containsAtLeast(CmdID, &SuppressLogList, 1, &[_]CmdID{cmd_id})) {
log.debug("packet {} was handled", .{cmd_id});
}
return;
}
}
inline for (DummyCmdList) |pair| {
if (pair[0] == cmd_id) {
try session.send_empty(pair[1]);
return;
}
}
log.warn("packet {} was ignored", .{cmd_id});
}

15
gameserver/src/main.zig Normal file
View file

@ -0,0 +1,15 @@
const std = @import("std");
const builtin = @import("builtin");
const network = @import("network.zig");
const handlers = @import("handlers.zig");
pub const std_options = .{
.log_level = switch (builtin.mode) {
.Debug => .debug,
else => .info,
},
};
pub fn main() !void {
try network.listen();
}

View file

@ -0,0 +1,254 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Data = @import("../data.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
// function to check the list if true
fn isInList(id: u32, list: []const u32) bool {
for (list) |item| {
if (item == id) {
return true;
}
}
return false;
}
pub const BattleManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) BattleManager {
return BattleManager{ .allocator = allocator };
}
pub fn createBattle(self: *BattleManager) !protocol.SceneBattleInfo {
const config = try Config.loadGameConfig(self.allocator, "config.json");
var battle = protocol.SceneBattleInfo.init(self.allocator);
const BattleBuff = protocol.BattleBuff;
// Avatar handler
for (config.avatar_config.items, 0..) |avatarConf, idx| {
var avatar = protocol.BattleAvatar.init(self.allocator);
avatar.id = avatarConf.id;
avatar.hp = avatarConf.hp * 100;
avatar.sp_bar = .{ .sp_cur = avatarConf.sp * 100, .sp_max = 10000 };
avatar.level = avatarConf.level;
avatar.rank = avatarConf.rank;
avatar.promotion = avatarConf.promotion;
avatar.avatar_type = .AVATAR_FORMAL_TYPE;
// Relics
for (avatarConf.relics.items) |relic| {
const r = try relicCoder(self.allocator, relic.id, relic.level, relic.main_affix_id, relic.stat1, relic.cnt1, relic.step1, relic.stat2, relic.cnt2, relic.step2, relic.stat3, relic.cnt3, relic.step3, relic.stat4, relic.cnt4, relic.step4);
try avatar.relic_list.append(r);
}
// Lightcone (LC)
const lc = protocol.BattleEquipment{
.id = avatarConf.lightcone.id,
.rank = avatarConf.lightcone.rank,
.level = avatarConf.lightcone.level,
.promotion = avatarConf.lightcone.promotion,
};
try avatar.equipment_list.append(lc);
//max trace
var talentLevel: u32 = 0;
const skill_list: []const u32 = if (isInList(avatar.id, &Data.Rem)) &Data.skills else &Data.skills_old;
for (skill_list) |elem| {
talentLevel = switch (elem) {
1 => 6,
2...4 => 10,
301, 302 => if (isInList(avatar.id, &Data.Rem)) 6 else 1,
else => 1,
};
const talent = protocol.AvatarSkillTree{ .point_id = avatar.id * 1000 + elem, .level = talentLevel };
try avatar.skilltree_list.append(talent);
}
// enable technique
if (avatarConf.use_technique) {
var targetIndexList = ArrayList(u32).init(self.allocator);
try targetIndexList.append(0);
//Add new ID when modifying for new patch
var buffedAvatarId = avatar.id;
if (avatar.id == 8004) {
buffedAvatarId = 8003;
} else if (avatar.id == 8006) {
buffedAvatarId = 8005;
} else if (avatar.id == 8008) {
buffedAvatarId = 8007;
}
for (Data.buffs_unlocked) |buffId| {
const idPrefix = buffId / 100;
if (idPrefix == buffedAvatarId) {
var buff = BattleBuff{
.id = buffId,
.level = 1,
.owner_index = @intCast(idx),
.wave_flag = 1,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
}
}
if (isInList(buffedAvatarId, &Data.IgnoreToughness)) {
var buff_tough = BattleBuff{
.id = 1000119, //for is_ignore toughness
.level = 1,
.owner_index = @intCast(idx),
.wave_flag = 1,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try buff_tough.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff_tough);
}
if (buffedAvatarId == 1224) {
var buff_march = protocol.BattleBuff{
.id = 122401, //for hunt march 7th tech
.level = 1,
.owner_index = @intCast(idx),
.wave_flag = 1,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try buff_march.dynamic_values.appendSlice(&[_]protocol.BattleBuff.DynamicValuesEntry{
.{ .key = .{ .Const = "#ADF_1" }, .value = 3 },
.{ .key = .{ .Const = "#ADF_2" }, .value = 3 },
});
try battle.buff_list.append(buff_march);
}
if (buffedAvatarId == 1310) {
var buff_firefly = BattleBuff{
.id = 1000112, //for firefly tech
.level = 1,
.owner_index = @intCast(idx),
.wave_flag = 1,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try buff_firefly.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff_firefly);
}
if (buffedAvatarId == 8007) {
var buff_rmc = BattleBuff{
.id = 800701, //for rmc tech
.level = 1,
.owner_index = @intCast(idx),
.wave_flag = 1,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try buff_rmc.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff_rmc);
}
}
try battle.pve_avatar_list.append(avatar);
}
// Basic battle info
battle.battle_id = config.battle_config.battle_id;
battle.stage_id = config.battle_config.stage_id;
battle.logic_random_seed = @intCast(@mod(std.time.timestamp(), 0xFFFFFFFF));
battle.rounds_limit = config.battle_config.cycle_count;
battle.monster_wave_length = @intCast(config.battle_config.monster_wave.items.len);
battle.world_level = 6;
// Monster handler
for (config.battle_config.monster_wave.items) |wave| {
var monster_wave = protocol.SceneMonsterWave.init(self.allocator);
monster_wave.wave_param = protocol.SceneMonsterWaveParam{ .level = config.battle_config.monster_level };
for (wave.items) |mob_id| {
try monster_wave.monster_list.append(.{ .monster_id = mob_id });
}
try battle.monster_wave_list.append(monster_wave);
}
// stage blessings
for (config.battle_config.blessings.items) |blessing| {
var targetIndexList = ArrayList(u32).init(self.allocator);
try targetIndexList.append(0);
var buff = protocol.BattleBuff{
.id = blessing,
.level = 1,
.owner_index = 0xffffffff,
.wave_flag = 0xffffffff,
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try buff.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(buff);
}
if (isInList(1407, Data.AllAvatars)) { //support Castorice
var targetIndexList = ArrayList(u32).init(self.allocator);
try targetIndexList.append(0);
var mazebuff_data = BattleBuff{
.id = 140703,
.level = 1,
.owner_index = 1,
.wave_flag = @intCast(config.battle_config.monster_wave.items.len),
.target_index_list = targetIndexList,
.dynamic_values = ArrayList(protocol.BattleBuff.DynamicValuesEntry).init(self.allocator),
};
try mazebuff_data.dynamic_values.append(.{ .key = .{ .Const = "SkillIndex" }, .value = 0 });
try battle.buff_list.append(mazebuff_data);
}
// PF/AS scoring
const BattleTargetInfoEntry = protocol.SceneBattleInfo.BattleTargetInfoEntry;
battle.battle_target_info = ArrayList(BattleTargetInfoEntry).init(self.allocator);
// target hardcode
var pfTargetHead = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(self.allocator) };
try pfTargetHead.battle_target_list.append(.{ .id = 10002, .progress = 0, .total_progress = 0 });
var pfTargetTail = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(self.allocator) };
try pfTargetTail.battle_target_list.append(.{ .id = 2001, .progress = 0, .total_progress = 0 });
try pfTargetTail.battle_target_list.append(.{ .id = 2002, .progress = 0, .total_progress = 0 });
var asTargetHead = protocol.BattleTargetList{ .battle_target_list = ArrayList(protocol.BattleTarget).init(self.allocator) };
try asTargetHead.battle_target_list.append(.{ .id = 90005, .progress = 0, .total_progress = 0 });
switch (battle.stage_id) {
// PF
30019000...30019100, 30021000...30021100, 30301000...30319000 => {
try battle.battle_target_info.append(.{ .key = 1, .value = pfTargetHead });
// fill blank target
for (2..5) |i| {
try battle.battle_target_info.append(.{ .key = @intCast(i) });
}
try battle.battle_target_info.append(.{ .key = 5, .value = pfTargetTail });
},
// AS
420100...420300 => {
try battle.battle_target_info.append(.{ .key = 1, .value = asTargetHead });
},
else => {},
}
return battle;
}
};
pub fn relicCoder(allocator: Allocator, id: u32, level: u32, main_affix_id: u32, stat1: u32, cnt1: u32, step1: u32, stat2: u32, cnt2: u32, step2: u32, stat3: u32, cnt3: u32, step3: u32, stat4: u32, cnt4: u32, step4: u32) !protocol.BattleRelic {
var relic = protocol.BattleRelic{
.id = id,
.main_affix_id = main_affix_id,
.level = level,
.sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
};
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat1, .cnt = cnt1, .step = step1 });
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat2, .cnt = cnt2, .step = step2 });
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat3, .cnt = cnt3, .step = step3 });
try relic.sub_affix_list.append(protocol.RelicAffix{ .affix_id = stat4, .cnt = cnt4, .step = step4 });
return relic;
}

View file

@ -0,0 +1,39 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Data = @import("../data.zig");
const UidGenerator = @import("../services/item.zig").UidGenerator;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub const LineupManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) LineupManager {
return LineupManager{ .allocator = allocator };
}
pub fn createLineup(self: *LineupManager) !protocol.LineupInfo {
const config = try Config.loadGameConfig(self.allocator, "config.json");
var lineup = protocol.LineupInfo.init(self.allocator);
lineup.mp = 5;
lineup.max_mp = 5;
lineup.name = .{ .Const = "CastoriceSR" };
for (config.avatar_config.items, 0..) |avatarConf, idx| {
var avatar = protocol.LineupAvatar.init(self.allocator);
avatar.id = avatarConf.id;
avatar.slot = @intCast(idx);
avatar.satiety = 0;
avatar.hp = avatarConf.hp * 100;
avatar.sp_bar = .{ .sp_cur = avatarConf.sp * 100, .sp_max = 10000 };
avatar.avatar_type = protocol.AvatarType.AVATAR_FORMAL_TYPE;
try lineup.avatar_list.append(avatar);
}
return lineup;
}
};

View file

@ -0,0 +1,110 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Data = @import("../data.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub const MultiPathManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) MultiPathManager {
return MultiPathManager{ .allocator = allocator };
}
pub fn createMultiPath(self: *MultiPathManager, skinID: u32) !protocol.GetMultiPathAvatarInfoScRsp {
var rsp = protocol.GetMultiPathAvatarInfoScRsp.init(self.allocator);
const config = try Config.loadGameConfig(self.allocator, "config.json");
const GeneratorType = UidGenerator();
const avatar_ids = [_][]const u32{
&[_]u32{ 8001, 8002 },
&[_]u32{ 8003, 8004 },
&[_]u32{ 8005, 8006 },
&[_]u32{ 8007, 8008 },
&[_]u32{1001},
&[_]u32{1224},
};
const avatar_types = [_]protocol.MultiPathAvatarType{
.GirlWarriorType, .GirlKnightType, .GirlShamanType,
.GirlMemoryType, .Mar_7thKnightType, .Mar_7thRogueType,
};
var indexes: [6]u32 = [_]u32{0} ** 6;
var counts: [6]u32 = [_]u32{0} ** 6;
var multis: [6]protocol.MultiPathAvatarInfo = undefined;
for (&multis, avatar_types, 0..) |*multi, avatar_type, i| {
std.debug.print("MULTIPATH AVATAR INDEX: {} IS {}\n", .{ i, avatar_type });
multi.* = protocol.MultiPathAvatarInfo.init(self.allocator);
multi.avatar_id = avatar_type;
if (avatar_type == .Mar_7thKnightType) {
multi.dressed_skin_id = skinID;
}
}
for (config.avatar_config.items) |avatar| {
for (0..avatar_ids.len) |i| {
counts[i] += 1;
for (avatar_ids[i]) |id| {
if (avatar.id == id) {
multis[i].rank = avatar.rank;
indexes[i] = counts[i] - 1;
}
}
}
}
var generators: [6]GeneratorType = undefined;
for (0..multis.len) |i| {
generators[i] = GeneratorType.init(indexes[i] * 7 + 1);
}
for (0..multis.len) |i| {
var multi = &multis[i];
var gen = &generators[i];
multi.path_equipment_id = indexes[i] * 7 + 1;
multi.equip_relic_list = ArrayList(protocol.EquipRelic).init(self.allocator);
for (0..6) |slot| {
try multi.equip_relic_list.append(.{
.relic_unique_id = gen.nextId(),
.type = @intCast(slot),
});
}
}
for (0..multis.len) |i| {
const skill_set = if (i == 3) &Data.skills else &Data.skills_old;
for (skill_set) |skill| {
const talent_level: u32 = if (skill == 1 or skill == 301 or skill == 302) 6 else if (skill <= 4) 10 else 1;
const point_id = if (avatar_ids[i].len > 1)
avatar_ids[i][1] * 1000 + skill
else
avatar_ids[i][0] * 1000 + skill;
const talent = protocol.AvatarSkillTree{
.point_id = point_id,
.level = talent_level,
};
try multis[i].multi_path_skill_tree.append(talent);
}
}
try rsp.multi_path_avatar_info_list.appendSlice(&multis);
try rsp.basic_type_id_list.appendSlice(&Data.MultiAvatar);
try rsp.cur_multi_path_avatar_type_map.append(.{ .key = 1001, .value = .Mar_7thKnightType });
try rsp.cur_multi_path_avatar_type_map.append(.{ .key = 8001, .value = .GirlMemoryType });
rsp.retcode = 0;
return rsp;
}
};
pub fn UidGenerator() type {
return struct {
current_id: u32,
const Self = @This();
pub fn init(initial_id: u32) Self {
return Self{ .current_id = initial_id };
}
pub fn nextId(self: *Self) u32 {
self.current_id +%= 1;
return self.current_id;
}
};
}

View file

@ -0,0 +1,265 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("../services/config.zig");
const Res_config = @import("../services/res_config.zig");
const Data = @import("../data.zig");
const UidGenerator = @import("../services/item.zig").UidGenerator;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub const SceneManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) SceneManager {
return SceneManager{ .allocator = allocator };
}
pub fn createScene(
self: *SceneManager,
plane_id: u32,
floor_id: u32,
entry_id: u32,
teleport_id: u32,
) !protocol.SceneInfo {
const config = try Config.loadGameConfig(self.allocator, "config.json");
const res_config = try Res_config.anchorLoader(self.allocator, "resources/res.json");
var generator = UidGenerator().init();
var scene_info = protocol.SceneInfo.init(self.allocator);
scene_info.plane_id = plane_id;
scene_info.floor_id = floor_id;
scene_info.entry_id = entry_id;
scene_info.leader_entity_id = 100000;
scene_info.world_id = 501;
scene_info.client_pos_version = 1;
var group_map = std.AutoHashMap(u32, protocol.SceneEntityGroupInfo).init(self.allocator);
defer group_map.deinit();
for (res_config.scene_config.items) |sceneConf| {
for (sceneConf.teleports.items) |teleConf| {
if (teleConf.teleportId == teleport_id) {
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
for (config.avatar_config.items, 0..) |avatarConf, idx| {
try scene_group.entity_list.append(.{
.InstId = 1,
.EntityId = @intCast(idx + 100000),
.entityCase_ = .{ .Actor = .{
.base_avatar_id = avatarConf.id,
.avatar_type = .AVATAR_FORMAL_TYPE,
.uid = 0,
.map_layer = 0,
} },
.Motion = .{
.pos = .{ .x = teleConf.pos.x, .y = teleConf.pos.y, .z = teleConf.pos.z },
.rot = .{ .x = teleConf.rot.x, .y = teleConf.rot.y, .z = teleConf.rot.z },
},
});
}
try scene_info.entity_group_list.append(scene_group);
}
}
if (scene_info.plane_id != 10000 and scene_info.plane_id != 10202 and sceneConf.planeID == scene_info.plane_id) {
for (sceneConf.props.items) |propConf| {
var scene_group = try getOrCreateGroup(&group_map, propConf.groupId, self.allocator);
var prop_info = protocol.ScenePropInfo.init(self.allocator);
prop_info.prop_id = propConf.propId;
prop_info.prop_state = propConf.propState;
try scene_group.entity_list.append(.{
.entityCase_ = .{ .Prop = prop_info },
.GroupId = scene_group.group_id,
.InstId = propConf.instId,
.EntityId = 0,
.Motion = .{
.pos = .{ .x = propConf.pos.x, .y = propConf.pos.y, .z = propConf.pos.z },
.rot = .{ .x = propConf.rot.x, .y = propConf.rot.y, .z = propConf.rot.z },
},
});
}
for (sceneConf.monsters.items) |monsConf| {
var scene_group = try getOrCreateGroup(&group_map, monsConf.groupId, self.allocator);
var monster_info = protocol.SceneNpcMonsterInfo.init(self.allocator);
monster_info.monster_id = monsConf.monsterId;
monster_info.event_id = monsConf.eventId;
monster_info.world_level = 6;
try scene_group.entity_list.append(.{
.entityCase_ = .{ .NpcMonster = monster_info },
.GroupId = scene_group.group_id,
.InstId = monsConf.instId,
.EntityId = generator.nextId(),
.Motion = .{
.pos = .{ .x = monsConf.pos.x, .y = monsConf.pos.y, .z = monsConf.pos.z },
.rot = .{ .x = monsConf.rot.x, .y = monsConf.rot.y, .z = monsConf.rot.z },
},
});
}
}
}
var iter = group_map.iterator();
while (iter.next()) |entry| {
try scene_info.entity_group_list.append(entry.value_ptr.*);
}
return scene_info;
}
fn getOrCreateGroup(group_map: *std.AutoHashMap(u32, protocol.SceneEntityGroupInfo), group_id: u32, allocator: std.mem.Allocator) !*protocol.SceneEntityGroupInfo {
if (group_map.getPtr(group_id)) |existing_group| {
return existing_group;
}
var new_group = protocol.SceneEntityGroupInfo.init(allocator);
new_group.state = 1;
new_group.group_id = group_id;
try group_map.put(group_id, new_group);
return group_map.getPtr(group_id).?;
}
};
pub const ChallengeSceneManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) ChallengeSceneManager {
return ChallengeSceneManager{ .allocator = allocator };
}
pub fn createScene(
self: *ChallengeSceneManager,
avatar_list: ArrayList(u32),
plane_id: u32,
floor_id: u32,
entry_id: u32,
world_id: u32,
monster_id: u32,
group_id: u32,
maze_group_id: u32,
) !protocol.SceneInfo {
const res_config = try Res_config.anchorLoader(self.allocator, "resources/res.json");
var generator = UidGenerator().init();
var scene_info = protocol.SceneInfo.init(self.allocator);
scene_info.game_mode_type = 4;
scene_info.plane_id = plane_id;
scene_info.floor_id = floor_id;
scene_info.entry_id = entry_id;
scene_info.leader_entity_id = 1;
scene_info.world_id = world_id;
try scene_info.group_state_list.append(protocol.SceneGroupState{
.group_id = maze_group_id,
.is_default = true,
});
{ // Character
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = 0;
for (avatar_list.items, 0..) |avatarConf, idx| {
const newidx = idx + 100000;
try scene_group.entity_list.append(.{
.InstId = 1,
.EntityId = @intCast(newidx),
.entityCase_ = .{
.Actor = .{
.base_avatar_id = avatarConf,
.avatar_type = .AVATAR_FORMAL_TYPE,
.uid = 1,
.map_layer = 0,
},
},
.Motion = .{ .pos = .{}, .rot = .{} },
});
}
try scene_info.entity_group_list.append(scene_group);
}
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID == scene_info.plane_id) {
for (sceneConf.monsters.items) |monsConf| { //create monster
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
if (monsConf.groupId == group_id) {
scene_group.group_id = group_id;
var monster_info = protocol.SceneNpcMonsterInfo.init(self.allocator);
monster_info.monster_id = monster_id;
monster_info.event_id = monsConf.eventId;
monster_info.world_level = 6;
try scene_group.entity_list.append(.{
.entityCase_ = .{ .NpcMonster = monster_info },
.GroupId = group_id,
.InstId = monsConf.instId,
.EntityId = generator.nextId(),
.Motion = .{
.pos = .{ .x = monsConf.pos.x, .y = monsConf.pos.y, .z = monsConf.pos.z },
.rot = .{ .x = monsConf.rot.x, .y = monsConf.rot.y, .z = monsConf.rot.z },
},
});
try scene_info.entity_group_list.append(scene_group);
}
}
}
}
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID == scene_info.plane_id) {
for (sceneConf.props.items) |propConf| { //create props
var scene_group = protocol.SceneEntityGroupInfo.init(self.allocator);
scene_group.state = 1;
scene_group.group_id = group_id;
var prop_info = protocol.ScenePropInfo.init(self.allocator);
prop_info.prop_id = propConf.propId;
prop_info.prop_state = propConf.propState;
try scene_group.entity_list.append(.{
.entityCase_ = .{ .Prop = prop_info },
.GroupId = group_id,
.InstId = propConf.instId,
.EntityId = 0,
.Motion = .{
.pos = .{ .x = propConf.pos.x, .y = propConf.pos.y, .z = propConf.pos.z },
.rot = .{ .x = propConf.rot.x, .y = propConf.rot.y, .z = propConf.rot.z },
},
});
try scene_info.entity_group_list.append(scene_group);
}
}
}
return scene_info;
}
};
pub const MazeMapManager = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) MazeMapManager {
return MazeMapManager{ .allocator = allocator };
}
pub fn setMazeMapData(
self: *MazeMapManager,
map_info: *protocol.SceneMapInfo,
floor_id: u32,
) !void {
const config = try Config.loadMapEntranceConfig(self.allocator, "resources/MapEntrance.json");
const res_config = try Res_config.anchorLoader(self.allocator, "resources/res.json");
var plane_ids = ArrayList(u32).init(self.allocator);
for (config.map_entrance_config.items) |entrConf| {
if (entrConf.floor_id == floor_id) {
try plane_ids.append(entrConf.plane_id);
}
}
map_info.maze_group_list = ArrayList(protocol.MazeGroup).init(self.allocator);
map_info.maze_prop_list = ArrayList(protocol.MazePropState).init(self.allocator);
for (res_config.scene_config.items) |resConf| {
for (plane_ids.items) |plane_id| {
if (resConf.planeID == plane_id) {
for (resConf.props.items) |propConf| {
try map_info.maze_group_list.append(protocol.MazeGroup{
.NOBKEONAKLE = ArrayList(u32).init(self.allocator),
.group_id = propConf.groupId,
});
try map_info.maze_prop_list.append(protocol.MazePropState{
.group_id = propConf.groupId,
.config_id = propConf.instId,
.state = propConf.propState,
});
}
}
}
}
}
};

View file

@ -0,0 +1,42 @@
const std = @import("std");
const Session = @import("Session.zig");
const Allocator = std.mem.Allocator;
pub fn listen() !void {
const addr = std.net.Address.parseIp4("0.0.0.0", 23301) catch unreachable;
var listener = try addr.listen(.{
.kernel_backlog = 100,
.reuse_address = true,
});
std.log.info("server is listening at {}", .{listener.listen_address});
while (true) {
const conn = listener.accept() catch continue;
errdefer conn.stream.close();
const thread = try std.Thread.spawn(.{}, runSession, .{ conn.address, conn.stream });
thread.detach();
}
}
fn runSession(address: std.net.Address, stream: std.net.Stream) !void {
std.log.info("new connection from {}", .{address});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() == .leak) std.log.err("memory leaks were detected for session at {}", .{address});
const allocator = gpa.allocator();
const session = try allocator.create(Session);
session.* = Session.init(address, stream, allocator);
if (session.*.run()) |_| {
std.log.info("client from {} disconnected", .{address});
} else |err| {
std.log.err("session disconnected with an error: {}", .{err});
}
allocator.destroy(session);
}

View file

@ -0,0 +1,186 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const Data = @import("../data.zig");
const UidGenerator = @import("item.zig").UidGenerator;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub var m7th: bool = true;
pub var mg: bool = true;
pub var mac: u32 = 4;
// function to check the list if true
fn isInList(id: u32, list: []const u32) bool {
for (list) |item| {
if (item == id) {
return true;
}
}
return false;
}
pub fn onGetAvatarData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const config = try Config.loadGameConfig(allocator, "config.json");
var generator = UidGenerator().init();
const req = try packet.getProto(protocol.GetAvatarDataCsReq, allocator);
var rsp = protocol.GetAvatarDataScRsp.init(allocator);
rsp.is_get_all = req.is_get_all;
for (Data.AllAvatars) |id| {
var avatar = protocol.Avatar.init(allocator);
avatar.base_avatar_id = id;
avatar.level = 80;
avatar.promotion = 6;
avatar.rank = 6;
avatar.has_taken_promotion_reward_list = ArrayList(u32).init(allocator);
for (1..6) |i| {
try avatar.has_taken_promotion_reward_list.append(@intCast(i));
}
var talentLevel: u32 = 0;
const skill_list: []const u32 = if (isInList(avatar.base_avatar_id, &Data.Rem)) &Data.skills else &Data.skills_old;
for (skill_list) |elem| {
talentLevel = switch (elem) {
1 => 6,
2...4 => 10,
301, 302 => if (isInList(avatar.base_avatar_id, &Data.Rem)) 6 else 1,
else => 1,
};
const talent = protocol.AvatarSkillTree{ .point_id = avatar.base_avatar_id * 1000 + elem, .level = talentLevel };
try avatar.skilltree_list.append(talent);
}
try rsp.avatar_list.append(avatar);
}
for (config.avatar_config.items) |avatarConf| { // rewrite data of avatar in config
var avatar = protocol.Avatar.init(allocator);
avatar.base_avatar_id = switch (avatarConf.id) {
8001...8008 => 8001,
1224 => 1001,
else => avatarConf.id,
};
avatar.level = avatarConf.level;
avatar.promotion = avatarConf.promotion;
avatar.rank = avatarConf.rank;
avatar.equipment_unique_id = generator.nextId();
avatar.equip_relic_list = ArrayList(protocol.EquipRelic).init(allocator);
for (0..6) |i| {
try avatar.equip_relic_list.append(.{
.relic_unique_id = generator.nextId(), // uid
.type = @intCast(i), // slot
});
}
var talentLevel: u32 = 0;
const skill_list: []const u32 = if (isInList(avatar.base_avatar_id, &Data.Rem)) &Data.skills else &Data.skills_old;
for (skill_list) |elem| {
talentLevel = switch (elem) {
1 => 6,
2...4 => 10,
301, 302 => if (isInList(avatar.base_avatar_id, &Data.Rem)) 6 else 1,
else => 1,
};
const talent = protocol.AvatarSkillTree{ .point_id = avatar.base_avatar_id * 1000 + elem, .level = talentLevel };
try avatar.skilltree_list.append(talent);
}
try rsp.avatar_list.append(avatar);
const avatarType: protocol.MultiPathAvatarType = @enumFromInt(avatarConf.id);
if (@intFromEnum(avatarType) > 1) {
try session.send(CmdID.CmdSetAvatarPathScRsp, protocol.SetAvatarPathScRsp{
.retcode = 0,
.avatar_id = avatarType,
});
}
}
try session.send(CmdID.CmdGetAvatarDataScRsp, rsp);
}
pub fn onGetBasicInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetBasicInfoScRsp.init(allocator);
rsp.Gender = 2;
rsp.IsGenderSet = true;
rsp.PlayerSettingInfo = .{};
try session.send(CmdID.CmdGetBasicInfoScRsp, rsp);
}
pub fn onSetAvatarPath(session: *Session, packet: *const Packet, allocator: Allocator) !void {
var rsp = protocol.SetAvatarPathScRsp.init(allocator);
const req = try packet.getProto(protocol.SetAvatarPathCsReq, allocator);
rsp.avatar_id = req.avatar_id;
if (rsp.avatar_id == protocol.MultiPathAvatarType.Mar_7thKnightType) {
m7th = false;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.Mar_7thRogueType) {
m7th = true;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.BoyWarriorType) {
mac = 1;
mg = false;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.BoyKnightType) {
mac = 2;
mg = false;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.BoyShamanType) {
mac = 3;
mg = false;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.BoyMemoryType) {
mac = 4;
mg = false;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.GirlWarriorType) {
mac = 1;
mg = true;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.GirlKnightType) {
mac = 2;
mg = true;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.GirlShamanType) {
mac = 3;
mg = true;
} else if (rsp.avatar_id == protocol.MultiPathAvatarType.GirlMemoryType) {
mac = 4;
mg = true;
}
var sync = protocol.AvatarPathChangedNotify.init(allocator);
if (req.avatar_id == protocol.MultiPathAvatarType.GirlMemoryType) {
sync.base_avatar_id = 8008;
} else if (req.avatar_id == protocol.MultiPathAvatarType.GirlShamanType) {
sync.base_avatar_id = 8006;
} else if (req.avatar_id == protocol.MultiPathAvatarType.GirlKnightType) {
sync.base_avatar_id = 8004;
} else if (req.avatar_id == protocol.MultiPathAvatarType.GirlWarriorType) {
sync.base_avatar_id = 8002;
}
sync.cur_multi_path_avatar_type = req.avatar_id;
try session.send(CmdID.CmdAvatarPathChangedNotify, sync);
try session.send(CmdID.CmdSetAvatarPathScRsp, rsp);
}
pub fn onDressAvatarSkin(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.DressAvatarSkinScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdDressAvatarSkinScRsp, rsp);
}
pub fn onTakeOffAvatarSkin(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.TakeOffAvatarSkinScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdTakeOffAvatarSkinScRsp, rsp);
}
pub fn onGetBigDataAll(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetBigDataAllRecommendCsReq, allocator);
var rsp = protocol.GetBigDataAllRecommendScRsp.init(allocator);
rsp.retcode = 0;
rsp.IANNEEIJAKH = req.IANNEEIJAKH;
std.debug.print("PRINT BIG DATA ALL REQ {}\n", .{req});
try session.send(CmdID.CmdGetBigDataAllRecommendScRsp, rsp);
}
pub fn onGetBigData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetBigDataRecommendCsReq, allocator);
var rsp = protocol.GetBigDataRecommendScRsp.init(allocator);
rsp.retcode = 0;
rsp.IANNEEIJAKH = req.IANNEEIJAKH;
rsp.EIGPMIBCIKG = req.EIGPMIBCIKG;
std.debug.print("PRINT BIG DATA REQ {}\n", .{req});
try session.send(CmdID.CmdGetBigDataRecommendScRsp, rsp);
}

View file

@ -0,0 +1,91 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const Data = @import("../data.zig");
const BattleManager = @import("../manager/battle_mgr.zig").BattleManager;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onStartCocoonStage(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartCocoonStageCsReq, allocator);
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
try session.send(CmdID.CmdStartCocoonStageScRsp, protocol.StartCocoonStageScRsp{
.retcode = 0,
.cocoon_id = req.cocoon_id,
.prop_entity_id = req.prop_entity_id,
.wave = req.wave,
.battle_info = battle,
});
}
pub fn onQuickStartCocoonStage(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.QuickStartCocoonStageCsReq, allocator);
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
try session.send(CmdID.CmdQuickStartCocoonStageScRsp, protocol.QuickStartCocoonStageScRsp{
.retcode = 0,
.cocoon_id = req.cocoon_id,
.wave = req.wave,
.battle_info = battle,
});
}
pub fn onQuickStartFarmElement(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.QuickStartFarmElementCsReq, allocator);
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
try session.send(CmdID.CmdQuickStartFarmElementScRsp, protocol.QuickStartFarmElementScRsp{
.retcode = 0,
.world_level = req.world_level,
.JDANOKNHNHL = req.JDANOKNHNHL,
.battle_info = battle,
});
}
pub fn onStartBattleCollege(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartBattleCollegeCsReq, allocator);
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
try session.send(CmdID.CmdStartBattleCollegeScRsp, protocol.StartBattleCollegeScRsp{
.retcode = 0,
.id = req.id,
.battle_info = battle,
});
}
pub fn onSceneCastSkill(session: *Session, packet: *const Packet, allocator: Allocator) !void {
var battle_mgr = BattleManager.init(allocator);
const battle = try battle_mgr.createBattle();
const req = try packet.getProto(protocol.SceneCastSkillCsReq, allocator);
var monster_battle_info_list = ArrayList(protocol.HitMonsterBattleInfo).init(allocator);
try monster_battle_info_list.appendSlice(&[_]protocol.HitMonsterBattleInfo{
.{
.target_monster_entity_id = 0,
.monster_battle_type = protocol.MonsterBattleType.MONSTER_BATTLE_TYPE_TRIGGER_BATTLE,
},
});
try session.send(CmdID.CmdSceneCastSkillScRsp, protocol.SceneCastSkillScRsp{
.retcode = 0,
.cast_entity_id = req.cast_entity_id,
.monster_battle_info = monster_battle_info_list,
.battle_info = if (req.assist_monster_entity_id_list.items.len > 0 or (req.attacked_by_entity_id >= 1 and req.attacked_by_entity_id <= 99)) battle else null,
});
}
pub fn onPVEBattleResult(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PVEBattleResultCsReq, allocator);
var rsp = protocol.PVEBattleResultScRsp.init(allocator);
rsp.battle_id = req.battle_id;
rsp.end_status = req.end_status;
try session.send(CmdID.CmdPVEBattleResultScRsp, rsp);
}
pub fn onSceneCastSkillCostMp(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SceneCastSkillCostMpCsReq, allocator);
try session.send(CmdID.CmdSceneCastSkillCostMpScRsp, protocol.SceneCastSkillCostMpScRsp{
.retcode = 0,
.cast_entity_id = req.cast_entity_id,
});
}

View file

@ -0,0 +1,207 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const Res_config = @import("res_config.zig");
const Data = @import("../data.zig");
const SceneManager = @import("../manager/scene_mgr.zig").SceneManager;
const ChallengeSceneManager = @import("../manager/scene_mgr.zig").ChallengeSceneManager;
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const NodeCheck = @import("../commands/value.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn UidGenerator() type {
return struct {
current_id: u32,
const Self = @This();
pub fn init() Self {
return Self{ .current_id = 100000 };
}
pub fn nextId(self: *Self) u32 {
self.current_id +%= 1; // Using wrapping addition
return self.current_id;
}
};
}
fn contains(list: *const std.ArrayListAligned(u32, null), value: u32) bool {
for (list.items) |item| {
if (item == value) {
return true;
}
}
return false;
}
pub fn onGetChallenge(session: *Session, _: *const Packet, allocator: Allocator) !void {
const challenge_config = try Config.loadChallengeConfig(allocator, "resources/ChallengeMazeConfig.json");
var rsp = protocol.GetChallengeScRsp.init(allocator);
rsp.retcode = 0;
for (challenge_config.challenge_config.items) |ids| {
var challenge = protocol.Challenge.init(allocator);
challenge.challenge_id = ids.id;
challenge.star = 1;
if (ids.id > 20000 and ids.id < 30000) {
challenge.score_id = 40000;
challenge.score_two = 40000;
}
try rsp.challenge_list.append(challenge);
}
try session.send(CmdID.CmdGetChallengeScRsp, rsp);
}
pub fn onGetChallengeGroupStatistics(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetChallengeGroupStatisticsCsReq, allocator);
var rsp = protocol.GetChallengeGroupStatisticsScRsp.init(allocator);
rsp.retcode = 0;
rsp.group_id = req.group_id;
try session.send(CmdID.CmdGetChallengeGroupStatisticsScRsp, rsp);
}
pub fn onLeaveChallenge(session: *Session, _: *const Packet, allocator: Allocator) !void {
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
var scene_manager = SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(20432, 20432001, 2043201, 1213);
try session.send(CmdID.CmdQuitBattleScNotify, protocol.QuitBattleScNotify{});
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.lineup = lineup,
.scene = scene_info,
});
try session.send(CmdID.CmdLeaveChallengeScRsp, protocol.LeaveChallengeScRsp{
.retcode = 0,
});
}
//TODO: IMPLEMENT CHALLENGE BUFF AND FIX MENU
pub fn onStartChallenge(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.StartChallengeCsReq, allocator);
var rsp = protocol.StartChallengeScRsp.init(allocator);
const challenge_config = try Config.loadChallengeConfig(allocator, "resources/ChallengeMazeConfig.json");
const entrance_config = try Config.loadMapEntranceConfig(allocator, "resources/MapEntrance.json");
const maze_config = try Config.loadMazePlaneConfig(allocator, "resources/MazePlane.json");
var avatarList = std.ArrayList(u32).init(allocator);
var lineup1 = protocol.LineupInfo.init(allocator);
lineup1.mp = 5;
lineup1.max_mp = 5;
lineup1.extra_lineup_type = protocol.ExtraLineupType.LINEUP_CHALLENGE;
for (req.first_lineup.items, 0..) |avt, idx| {
var list = protocol.LineupAvatar.init(allocator);
try avatarList.append(avt);
list.id = avt;
list.slot = @intCast(idx);
list.satiety = 0;
list.avatar_type = protocol.AvatarType.AVATAR_FORMAL_TYPE;
list.hp = 10000;
list.sp_bar = .{ .sp_cur = 10000, .sp_max = 10000 };
try lineup1.avatar_list.append(list);
}
try rsp.lineup_list.append(lineup1);
var avatarList2 = std.ArrayList(u32).init(allocator);
var lineup2 = protocol.LineupInfo.init(allocator);
lineup2.mp = 5;
lineup2.max_mp = 5;
lineup2.extra_lineup_type = protocol.ExtraLineupType.LINEUP_CHALLENGE_2;
for (req.second_lineup.items, 0..) |avt, idx| {
var list = protocol.LineupAvatar.init(allocator);
try avatarList2.append(avt);
list.id = avt;
list.slot = @intCast(idx);
list.satiety = 0;
list.avatar_type = protocol.AvatarType.AVATAR_FORMAL_TYPE;
list.hp = 10000;
list.sp_bar = .{ .sp_cur = 10000, .sp_max = 10000 };
try lineup2.avatar_list.append(list);
}
try rsp.lineup_list.append(lineup2);
var cur_challenge_info = protocol.CurChallenge.init(allocator);
cur_challenge_info.challenge_id = req.challenge_id;
cur_challenge_info.score_id = if (req.challenge_id > 20000 and req.challenge_id < 30000) 40000 else 0;
cur_challenge_info.score_two = 0;
cur_challenge_info.status = protocol.ChallengeStatus.CHALLENGE_DOING;
cur_challenge_info.extra_lineup_type = if (NodeCheck.challenge_node == 0) protocol.ExtraLineupType.LINEUP_CHALLENGE else protocol.ExtraLineupType.LINEUP_CHALLENGE_2;
var planeID: u32 = 0;
var floorID: u32 = 0;
var entryID: u32 = 0;
var worldID: u32 = 0;
var monsterID: u32 = 0;
var groupID: u32 = 0;
var maze_groupID: u32 = 0;
if (NodeCheck.challenge_node == 0) {
for (challenge_config.challenge_config.items) |challengeConf| {
if (challengeConf.id == req.challenge_id) {
std.debug.print("TRACING CONFIG ID {} WITH CHALLENGE ID {}\n", .{ challengeConf.id, req.challenge_id });
for (entrance_config.map_entrance_config.items) |entrance| {
if (entrance.id == challengeConf.map_entrance_id) {
for (maze_config.maze_plane_config.items) |maze| {
if (contains(&maze.floor_id_list, entrance.floor_id)) {
floorID = entrance.floor_id;
worldID = maze.world_id;
monsterID = challengeConf.npc_monster_id_list1.items[challengeConf.npc_monster_id_list1.items.len - 1];
groupID = challengeConf.maze_group_id1;
maze_groupID = challengeConf.maze_group_id1;
planeID = maze.challenge_plane_id;
entryID = challengeConf.map_entrance_id;
}
}
}
}
}
}
} else {
for (challenge_config.challenge_config.items) |challengeConf| {
if (challengeConf.id == req.challenge_id) {
std.debug.print("TRACING CONFIG ID {} WITH CHALLENGE ID {}\n", .{ challengeConf.id, req.challenge_id });
for (entrance_config.map_entrance_config.items) |entrance| {
if (entrance.id == challengeConf.map_entrance_id2) {
for (maze_config.maze_plane_config.items) |maze| {
if (contains(&maze.floor_id_list, entrance.floor_id)) {
floorID = entrance.floor_id;
worldID = maze.world_id;
monsterID = challengeConf.npc_monster_id_list2.items[challengeConf.npc_monster_id_list2.items.len - 1];
groupID = challengeConf.maze_group_id2;
maze_groupID = challengeConf.maze_group_id2;
planeID = maze.challenge_plane_id;
entryID = challengeConf.map_entrance_id2;
}
}
}
}
}
}
}
var scene_PF_manager = ChallengeSceneManager.init(allocator);
const scene_info = try scene_PF_manager.createScene(
if (NodeCheck.challenge_node == 0) avatarList else avatarList2,
planeID,
floorID,
entryID,
worldID,
monsterID,
groupID,
maze_groupID,
);
rsp.retcode = 0;
rsp.scene = scene_info;
rsp.cur_challenge = cur_challenge_info;
try session.send(CmdID.CmdStartChallengeScRsp, rsp);
std.debug.print("SEND PLANE ID {} FLOOR ID {} ENTRY ID {} GROUP ID {} MAZE GROUP ID {}\n", .{ planeID, floorID, entryID, groupID, maze_groupID });
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.lineup = if (NodeCheck.challenge_node == 0) lineup1 else lineup2,
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_DIMENSION_MERGE,
.scene = scene_info,
});
}

View file

@ -0,0 +1,116 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const commandhandler = @import("../command.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const B64Decoder = std.base64.standard.Decoder;
const EmojiList = [_]u32{};
pub fn onGetFriendListInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetFriendListInfoScRsp.init(allocator);
rsp.retcode = 0;
var assist_list = ArrayList(protocol.AssistSimpleInfo).init(allocator);
try assist_list.appendSlice(&[_]protocol.AssistSimpleInfo{
.{ .Pos = 0, .Level = 80, .AvatarId = 1403, .DressedSkinId = 0 },
.{ .Pos = 1, .Level = 80, .AvatarId = 1407, .DressedSkinId = 0 },
.{ .Pos = 2, .Level = 80, .AvatarId = 1001, .DressedSkinId = 1100101 },
});
var friend = protocol.FriendSimpleInfo.init(allocator);
friend.playing_state = .PLAYING_CHALLENGE_BOSS;
friend.create_time = 0; //timestamp
friend.remark_name = .{ .Const = "ReversedRooms" }; //friend_custom_nickname
friend.is_marked = true;
friend.player_simple_info = protocol.PlayerSimpleInfo{
.signature = .{ .Const = "https://discord.gg/reversedrooms" },
.nickname = .{ .Const = "CipherSR" },
.level = 70,
.uid = 2000,
.head_icon = 200136,
.ANPLLAOBFJI = 253000,
.chat_bubble_id = 220008,
.assist_simple_info = assist_list,
.platform_type = protocol.PlatformType.ANDROID,
.online_status = protocol.FriendOnlineStatus.FRIEND_ONLINE_STATUS_ONLINE,
};
try rsp.friend_list.append(friend);
try session.send(CmdID.CmdGetFriendListInfoScRsp, rsp);
}
pub fn onChatEmojiList(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetChatEmojiListScRsp.init(allocator);
rsp.retcode = 0;
try rsp.chat_emoji_list.appendSlice(&EmojiList);
try session.send(CmdID.CmdGetChatEmojiListScRsp, rsp);
}
pub fn onPrivateChatHistory(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPrivateChatHistoryScRsp.init(allocator);
rsp.retcode = 0;
rsp.target_side = 1;
rsp.contact_side = 2000;
try rsp.chat_message_list.appendSlice(&[_]protocol.ChatMessageData{
.{
.content = .{ .Const = "/help for command list" },
.message_type = .MSG_TYPE_CUSTOM_TEXT,
.create_time = 0,
.sender_id = 2000,
},
.{
.content = .{ .Const = "to use command, use '/' first" },
.message_type = .MSG_TYPE_CUSTOM_TEXT,
.create_time = 0,
.sender_id = 2000,
},
.{
.extra_id = 122004,
.message_type = .MSG_TYPE_EMOJI,
.create_time = 0,
.sender_id = 2000,
},
});
try session.send(CmdID.CmdGetPrivateChatHistoryScRsp, rsp);
}
pub fn onSendMsg(session: *Session, packet: *const Packet, allocator: Allocator) !void {
std.debug.print("Received packet: {any}\n", .{packet});
const req = protocol.SendMsgCsReq.init(allocator);
std.debug.print("Decoded request: {any}\n", .{req});
std.debug.print("Raw packet body: {any}\n", .{packet.body});
const msg_text = switch (req.message_text) {
.Empty => "",
.Owned => |owned| owned.str,
.Const => |const_str| const_str,
};
var msg_text2: []const u8 = "";
if (packet.body.len > 9 and packet.body[8] == 47) {
msg_text2 = packet.body[8 .. packet.body.len - 2];
}
std.debug.print("Manually extracted message text: '{s}'\n", .{msg_text2});
std.debug.print("Message Text 1: {any}\n", .{msg_text});
if (msg_text2.len > 0) {
if (std.mem.indexOf(u8, msg_text2, "/") != null) {
std.debug.print("Message contains a '/'\n", .{});
try commandhandler.handleCommand(session, msg_text2, allocator);
} else {
std.debug.print("Message does not contain a '/'\n", .{});
try commandhandler.sendMessage(session, msg_text2, allocator);
}
} else {
std.debug.print("Empty message received\n", .{});
}
var rsp = protocol.SendMsgScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdSendMsgScRsp, rsp);
}

View file

@ -0,0 +1,338 @@
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const std = @import("std");
const BattleConfig = struct {
battle_id: u32,
stage_id: u32,
cycle_count: u32,
monster_wave: ArrayList(ArrayList(u32)),
monster_level: u32,
blessings: ArrayList(u32),
};
const ExtraMazeBuff = struct {
enable: bool,
mazebuff: ArrayList(u32),
};
const Lightcone = struct {
id: u32,
rank: u32,
level: u32,
promotion: u32,
};
pub const Relic = struct {
id: u32,
level: u32,
main_affix_id: u32,
sub_count: u32,
stat1: u32,
cnt1: u32,
step1: u32,
stat2: u32,
cnt2: u32,
step2: u32,
stat3: u32,
cnt3: u32,
step3: u32,
stat4: u32,
cnt4: u32,
step4: u32,
};
const Avatar = struct {
id: u32,
hp: u32,
sp: u32,
level: u32,
promotion: u32,
rank: u32,
lightcone: Lightcone,
relics: ArrayList(Relic),
use_technique: bool,
};
const ChallengeConfig = struct {
id: u32, //
npc_monster_id_list1: ArrayList(u32), //
npc_monster_id_list2: ArrayList(u32), //
event_id_list1: ArrayList(u32), //
event_id_list2: ArrayList(u32), //
map_entrance_id: u32, //
map_entrance_id2: u32, //
maze_group_id1: u32, //
maze_group_id2: u32, //
};
const MapEntrance = struct {
floor_id: u32,
id: u32,
plane_id: u32,
begin_main_mission_idlist: ArrayList(u32),
finish_main_mission_idlist: ArrayList(u32),
finish_sub_mission_idlist: ArrayList(u32),
};
const MazePlane = struct {
floor_id_list: ArrayList(u32),
start_floor_id: u32,
challenge_plane_id: u32,
world_id: u32,
};
pub const GameConfig = struct {
battle_config: BattleConfig,
avatar_config: ArrayList(Avatar),
};
pub const ChallengeMazeConfig = struct {
challenge_config: ArrayList(ChallengeConfig),
};
pub const MapEntranceConfig = struct {
map_entrance_config: ArrayList(MapEntrance),
};
pub const MazePlaneConfig = struct {
maze_plane_config: ArrayList(MazePlane),
};
const ErrorSet = error{ CommandError, SystemResources, Unexpected, AccessDenied, WouldBlock, ConnectionResetByPeer, OutOfMemory, DiskQuota, FileTooBig, InputOutput, NoSpaceLeft, DeviceBusy, InvalidArgument, BrokenPipe, OperationAborted, NotOpenForWriting, LockViolation, Overflow, InvalidCharacter, ProcessFdQuotaExceeded, SystemFdQuotaExceeded, SymLinkLoop, NameTooLong, FileNotFound, NotDir, NoDevice, SharingViolation, PathAlreadyExists, PipeBusy, InvalidUtf8, InvalidWtf8, BadPathName, NetworkNotFound, AntivirusInterference, IsDir, FileLocksNotSupported, FileBusy, ConnectionTimedOut, NotOpenForReading, SocketNotConnected, Unseekable, UnexpectedToken, InvalidNumber, InvalidEnumTag, DuplicateField, UnknownField, MissingField, LengthMismatch, SyntaxError, UnexpectedEndOfInput, BufferUnderrun, ValueTooLong, InsufficientTokens, InvalidFormat };
pub fn loadConfig(
comptime ConfigType: type,
comptime parseFn: fn (std.json.Value, Allocator) ErrorSet!ConfigType,
allocator: Allocator,
filename: []const u8,
) ErrorSet!ConfigType {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
const buffer = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer, .{});
defer json_tree.deinit();
const root = json_tree.value;
return try parseFn(root, allocator);
}
// Specialized loaders
pub fn loadGameConfig(allocator: Allocator, filename: []const u8) ErrorSet!GameConfig {
return loadConfig(GameConfig, parseConfig, allocator, filename);
}
pub fn loadChallengeConfig(allocator: Allocator, filename: []const u8) ErrorSet!ChallengeMazeConfig {
return loadConfig(ChallengeMazeConfig, parseChallengeConfig, allocator, filename);
}
pub fn loadMapEntranceConfig(allocator: Allocator, filename: []const u8) ErrorSet!MapEntranceConfig {
return loadConfig(MapEntranceConfig, parseMapEntranceConfig, allocator, filename);
}
pub fn loadMazePlaneConfig(allocator: Allocator, filename: []const u8) ErrorSet!MazePlaneConfig {
return loadConfig(MazePlaneConfig, parseMazePlaneConfig, allocator, filename);
}
pub fn parseConfig(root: std.json.Value, allocator: Allocator) ErrorSet!GameConfig {
const battle_config_json = root.object.get("battle_config").?;
var battle_config = BattleConfig{
.battle_id = @intCast(battle_config_json.object.get("battle_id").?.integer),
.stage_id = @intCast(battle_config_json.object.get("stage_id").?.integer),
.cycle_count = @intCast(battle_config_json.object.get("cycle_count").?.integer),
.monster_wave = ArrayList(ArrayList(u32)).init(allocator),
.monster_level = @intCast(battle_config_json.object.get("monster_level").?.integer),
.blessings = ArrayList(u32).init(allocator),
};
std.debug.print("loading config stageID = {}\n", .{battle_config.stage_id});
for (battle_config_json.object.get("monster_wave").?.array.items) |wave| {
var wave_list = ArrayList(u32).init(allocator);
for (wave.array.items) |monster| {
try wave_list.append(@intCast(monster.integer));
}
try battle_config.monster_wave.append(wave_list);
}
for (battle_config_json.object.get("blessings").?.array.items) |blessing| {
try battle_config.blessings.append(@intCast(blessing.integer));
}
var avatar_config = ArrayList(Avatar).init(allocator);
for (root.object.get("avatar_config").?.array.items) |avatar_json| {
var avatar = Avatar{
.id = @intCast(avatar_json.object.get("id").?.integer),
.hp = @intCast(avatar_json.object.get("hp").?.integer),
.sp = @intCast(avatar_json.object.get("sp").?.integer),
.level = @intCast(avatar_json.object.get("level").?.integer),
.promotion = @intCast(avatar_json.object.get("promotion").?.integer),
.rank = @intCast(avatar_json.object.get("rank").?.integer),
.lightcone = undefined,
.relics = ArrayList(Relic).init(allocator),
.use_technique = avatar_json.object.get("use_technique").?.bool,
};
const lightcone_json = avatar_json.object.get("lightcone").?;
avatar.lightcone = Lightcone{
.id = @intCast(lightcone_json.object.get("id").?.integer),
.rank = @intCast(lightcone_json.object.get("rank").?.integer),
.level = @intCast(lightcone_json.object.get("level").?.integer),
.promotion = @intCast(lightcone_json.object.get("promotion").?.integer),
};
for (avatar_json.object.get("relics").?.array.items) |relic_str| {
const relic = try parseRelic(relic_str.string, allocator);
try avatar.relics.append(relic);
}
try avatar_config.append(avatar);
}
return GameConfig{
.battle_config = battle_config,
.avatar_config = avatar_config,
};
}
fn parseChallengeConfig(root: std.json.Value, allocator: Allocator) ErrorSet!ChallengeMazeConfig {
var challenge_config = ArrayList(ChallengeConfig).init(allocator);
for (root.object.get("challenge_config").?.array.items) |challenge_json| {
var challenge = ChallengeConfig{
.id = @intCast(challenge_json.object.get("ID").?.integer),
.npc_monster_id_list1 = ArrayList(u32).init(allocator),
.npc_monster_id_list2 = ArrayList(u32).init(allocator),
.event_id_list1 = ArrayList(u32).init(allocator),
.event_id_list2 = ArrayList(u32).init(allocator),
.map_entrance_id = @intCast(challenge_json.object.get("MapEntranceID").?.integer),
.map_entrance_id2 = @intCast(challenge_json.object.get("MapEntranceID2").?.integer),
.maze_group_id1 = @intCast(challenge_json.object.get("MazeGroupID1").?.integer),
.maze_group_id2 = @intCast(challenge_json.object.get("MazeGroupID2").?.integer),
};
for (challenge_json.object.get("NpcMonsterIDList1").?.array.items) |npc1| {
try challenge.npc_monster_id_list1.append(@intCast(npc1.integer));
}
for (challenge_json.object.get("NpcMonsterIDList2").?.array.items) |npc2| {
try challenge.npc_monster_id_list2.append(@intCast(npc2.integer));
}
for (challenge_json.object.get("EventIDList1").?.array.items) |event1| {
try challenge.event_id_list1.append(@intCast(event1.integer));
}
for (challenge_json.object.get("EventIDList2").?.array.items) |event2| {
try challenge.event_id_list2.append(@intCast(event2.integer));
}
try challenge_config.append(challenge);
}
return ChallengeMazeConfig{
.challenge_config = challenge_config,
};
}
fn parseMapEntranceConfig(root: std.json.Value, allocator: Allocator) ErrorSet!MapEntranceConfig {
var map_entrance_config = ArrayList(MapEntrance).init(allocator);
for (root.object.get("map_entrance_config").?.array.items) |mapEntrance| {
var entrance = MapEntrance{
.id = @intCast(mapEntrance.object.get("ID").?.integer),
.floor_id = @intCast(mapEntrance.object.get("FloorID").?.integer),
.plane_id = @intCast(mapEntrance.object.get("PlaneID").?.integer),
.begin_main_mission_idlist = ArrayList(u32).init(allocator),
.finish_main_mission_idlist = ArrayList(u32).init(allocator),
.finish_sub_mission_idlist = ArrayList(u32).init(allocator),
};
for (mapEntrance.object.get("BeginMainMissionList").?.array.items) |id| {
try entrance.begin_main_mission_idlist.append(@intCast(id.integer));
}
for (mapEntrance.object.get("FinishMainMissionList").?.array.items) |id| {
try entrance.finish_main_mission_idlist.append(@intCast(id.integer));
}
for (mapEntrance.object.get("FinishSubMissionList").?.array.items) |id| {
try entrance.finish_sub_mission_idlist.append(@intCast(id.integer));
}
try map_entrance_config.append(entrance);
}
return MapEntranceConfig{
.map_entrance_config = map_entrance_config,
};
}
fn parseMazePlaneConfig(root: std.json.Value, allocator: Allocator) ErrorSet!MazePlaneConfig {
var maze_plane_config = ArrayList(MazePlane).init(allocator);
for (root.object.get("maze_plane_config").?.array.items) |id| {
var maze = MazePlane{
.start_floor_id = @intCast(id.object.get("StartFloorID").?.integer),
.challenge_plane_id = @intCast(id.object.get("PlaneID").?.integer),
.world_id = @intCast(id.object.get("WorldID").?.integer),
.floor_id_list = ArrayList(u32).init(allocator),
};
for (id.object.get("FloorIDList").?.array.items) |list| {
try maze.floor_id_list.append(@intCast(list.integer));
}
try maze_plane_config.append(maze);
}
return MazePlaneConfig{
.maze_plane_config = maze_plane_config,
};
}
fn parseRelic(relic_str: []const u8, allocator: Allocator) !Relic {
var tokens = ArrayList([]const u8).init(allocator);
defer tokens.deinit();
var iterator = std.mem.tokenize(u8, relic_str, ",");
while (iterator.next()) |token| {
try tokens.append(token);
}
const tokens_slice = tokens.items;
if (tokens_slice.len < 8) {
std.debug.print("relic parsing error: {s}\n", .{relic_str});
return error.InsufficientTokens;
}
const stat1 = try parseStatCount(tokens_slice[4]);
const stat2 = try parseStatCount(tokens_slice[5]);
const stat3 = try parseStatCount(tokens_slice[6]);
const stat4 = try parseStatCount(tokens_slice[7]);
const relic = Relic{
.id = try std.fmt.parseInt(u32, tokens_slice[0], 10),
.level = try std.fmt.parseInt(u32, tokens_slice[1], 10),
.main_affix_id = try std.fmt.parseInt(u32, tokens_slice[2], 10),
.sub_count = try std.fmt.parseInt(u32, tokens_slice[3], 10),
.stat1 = stat1.stat,
.cnt1 = stat1.count,
.step1 = stat1.step,
.stat2 = stat2.stat,
.cnt2 = stat2.count,
.step2 = stat2.step,
.stat3 = stat3.stat,
.cnt3 = stat3.count,
.step3 = stat3.step,
.stat4 = stat4.stat,
.cnt4 = stat4.count,
.step4 = stat4.step,
};
return relic;
}
const StatCount = struct {
stat: u32,
count: u32,
step: u32,
};
fn parseStatCount(token: []const u8) !StatCount {
if (std.mem.indexOfScalar(u8, token, ':')) |first_colon| {
if (std.mem.indexOfScalar(u8, token[first_colon + 1 ..], ':')) |second_colon_offset| {
const second_colon = first_colon + 1 + second_colon_offset;
const stat = try std.fmt.parseInt(u32, token[0..first_colon], 10);
const count = try std.fmt.parseInt(u32, token[first_colon + 1 .. second_colon], 10);
const step = try std.fmt.parseInt(u32, token[second_colon + 1 ..], 10);
return StatCount{ .stat = stat, .count = count, .step = step };
} else {
return error.InvalidFormat;
}
} else {
return error.InvalidFormat;
}
}

View file

@ -0,0 +1,29 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetActivity(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetActivityScheduleConfigScRsp.init(allocator);
for (Data.EventList) |id| {
var activ_list = protocol.ActivityScheduleData.init(allocator);
activ_list.begin_time = 0;
activ_list.end_time = 1924992000;
if (id >= 100000) {
activ_list.activity_id = ((id % 100000) * 100) + 1;
} else {
activ_list.activity_id = id * 100 + 1;
}
activ_list.panel_id = id;
try rsp.schedule_data.append(activ_list);
}
rsp.retcode = 0;
try session.send(CmdID.CmdGetActivityScheduleConfigScRsp, rsp);
}

View file

@ -0,0 +1,222 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const GachaData = @import("../commands/value.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
// function to check the list if true
fn isInList(id: u32, list: []const u32) bool {
for (list) |item| {
if (item == id) {
return true;
}
}
return false;
}
pub fn onGetGachaInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var info = ArrayList(protocol.GachaCeilingAvatar).init(allocator);
for (GachaData.StandardBanner) |id| {
try info.appendSlice(&[_]protocol.GachaCeilingAvatar{
.{ .RepeatedCnt = 300, .AvatarId = id },
});
}
var gacha_info = protocol.GachaInfo.init(allocator);
gacha_info.begin_time = 0;
gacha_info.end_time = 2524608000;
gacha_info.gacha_ceiling = .{
.avatar_list = info,
.is_claimed = false,
.ceiling_num = 200,
};
gacha_info.KMNJNMJFGBG = 1;
gacha_info.GDIFAAHIFBH = 3;
gacha_info.gacha_id = 1001; // standard banner
var rsp = protocol.GetGachaInfoScRsp.init(allocator);
rsp.retcode = 0;
rsp.DJNDMNPEBKA = 20;
rsp.NOPBEBKHIKA = 20;
rsp.NBELNOIPOEK = 900;
rsp.gacha_random = 0;
try rsp.gacha_info_list.append(gacha_info);
try session.send(CmdID.CmdGetGachaInfoScRsp, rsp);
}
pub fn onBuyGoods(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.BuyGoodsCsReq, allocator);
var rsp = protocol.BuyGoodsScRsp.init(allocator);
var item = ArrayList(protocol.Item).init(allocator);
try item.appendSlice(&[_]protocol.Item{.{
.ItemId = 101,
.Num = 100,
}});
rsp.retcode = 0;
rsp.GoodsId = req.goods_id;
rsp.GoodsBuyTimes = req.goods_num;
rsp.ShopId = 0;
rsp.ReturnItemList = .{ .ItemList_ = item };
try session.send(CmdID.CmdBuyGoodsScRsp, rsp);
}
pub fn onGetShopList(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetShopListScRsp.init(allocator);
var shop = ArrayList(protocol.Shop).init(allocator);
var goods = ArrayList(protocol.Goods).init(allocator);
try shop.appendSlice(&[_]protocol.Shop{.{
.ShopId = 1000,
.GoodsList = goods,
}});
try goods.appendSlice(&[_]protocol.Goods{.{
.GoodsId = 101001,
.ItemId = 101,
.BuyTimes = 0,
}});
rsp.retcode = 0;
rsp.ShopType = 101;
rsp.ShopList = shop;
try session.send(CmdID.CmdGetShopListScRsp, rsp);
}
pub fn onExchangeHcoin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ExchangeHcoinCsReq, allocator);
var rsp = protocol.ExchangeHcoinScRsp.init(allocator);
rsp.Num = req.Num;
rsp.retcode = 0;
try session.send(CmdID.CmdExchangeHcoinScRsp, rsp);
}
var five_star_pity: u32 = 0;
var four_star_pity: u32 = 0;
var guaranteed_five_star_rate_up: bool = false;
var guaranteed_four_star_rate_up: bool = false;
var avatar_list_cached: ?std.ArrayList(u32) = null;
var lightcone_list_3_cached: ?std.ArrayList(u32) = null;
var lightcone_list_4_cached: ?std.ArrayList(u32) = null;
fn pow(base: f64, exp: f64) f64 {
return @exp(exp * @log(base));
}
fn getFiveStarRate(gacha_count: u32) f64 {
if (gacha_count < 21) {
return 0.02;
}
if (gacha_count < 72) {
return 0.008;
}
const excess_pulls = @as(f64, @floatFromInt(gacha_count - 71));
return 0.008 + (1.0 - 0.008) * pow(excess_pulls / 18.0, 2.8);
}
fn getFourStarRate(gacha_count: u32) f64 {
if (gacha_count < 6) {
return 0.055;
}
const excess_pulls = @as(f64, @floatFromInt(gacha_count - 5));
return 0.055 + (1.0 - 0.055) * pow(excess_pulls / 3.5, 2.2);
}
fn pickRandomId(rnd: *std.rand.Random, banner: []const u32) u32 {
return banner[rnd.int(usize) % banner.len];
}
pub fn onDoGacha(session: *Session, packet: *const Packet, allocator: std.mem.Allocator) !void {
const req = try packet.getProto(protocol.DoGachaCsReq, allocator);
var rsp = protocol.DoGachaScRsp.init(allocator);
var seed: u64 = undefined;
try std.posix.getrandom(std.mem.asBytes(&seed));
var prng = std.rand.DefaultPrng.init(seed);
var rnd = prng.random();
var selected_ids = std.ArrayList(u32).init(allocator);
defer selected_ids.deinit();
var got_four_star = false;
for (0..req.gacha_num) |_| {
const five_star_rate = getFiveStarRate(five_star_pity);
const four_star_rate = getFourStarRate(four_star_pity);
const random_value = rnd.float(f64);
var selected_banner: []const u32 = &Data.LightconeList_3;
var is_five_star = false;
var is_four_star = false;
if (random_value < five_star_rate or five_star_pity == 89) {
is_five_star = true;
five_star_pity = 0;
if (guaranteed_five_star_rate_up) {
selected_banner = &GachaData.RateUp;
guaranteed_five_star_rate_up = false;
} else {
if (rnd.boolean()) {
selected_banner = &GachaData.RateUp;
} else {
selected_banner = &GachaData.StandardBanner;
guaranteed_five_star_rate_up = true;
}
}
} else if (four_star_pity == 9 or random_value < (five_star_rate + four_star_rate)) {
is_four_star = true;
four_star_pity = 0;
got_four_star = true;
if (guaranteed_four_star_rate_up or rnd.float(f64) < 0.70) {
selected_banner = &GachaData.RateUpFourStars;
guaranteed_four_star_rate_up = false;
} else {
if (rnd.boolean()) {
selected_banner = &Data.AvatarList;
} else {
selected_banner = &Data.LightconeList_4;
}
guaranteed_four_star_rate_up = true;
}
} else {
four_star_pity += 1;
}
five_star_pity += 1;
try selected_ids.append(pickRandomId(&rnd, selected_banner));
}
if (req.gacha_num > 1 and !got_four_star) {
selected_ids.items[rnd.int(usize) % selected_ids.items.len] = pickRandomId(&rnd, &GachaData.RateUpFourStars);
}
for (selected_ids.items) |id| {
var gacha_item = protocol.GachaItem.init(allocator);
gacha_item.gacha_item = .{ .ItemId = id };
gacha_item.is_new = false;
var back_item = std.ArrayList(protocol.Item).init(allocator);
var transfer_item = std.ArrayList(protocol.Item).init(allocator);
if (id < 10000) {
if (isInList(id, &GachaData.RateUp) or isInList(id, &GachaData.StandardBanner)) {
try transfer_item.appendSlice(&[_]protocol.Item{
.{ .ItemId = id + 10000, .Num = 1 },
.{ .ItemId = 252, .Num = 20 },
});
} else {
try transfer_item.append(.{ .ItemId = 252, .Num = 20 });
}
}
try back_item.append(.{ .ItemId = 252, .Num = 20 });
gacha_item.transfer_item_list = .{ .ItemList_ = transfer_item };
gacha_item.token_item = .{ .ItemList_ = back_item };
try rsp.gacha_item_list.append(gacha_item);
}
rsp.gacha_num = req.gacha_num;
rsp.gacha_id = req.gacha_id;
rsp.ceiling_num = 200;
rsp.KMNJNMJFGBG = 1;
rsp.NOPBEBKHIKA = 20;
rsp.GDIFAAHIFBH = 3;
rsp.retcode = 0;
std.debug.print("FIVE STAR PITY: {}, (RATE: {d:.4}%)\n", .{ five_star_pity, getFiveStarRate(five_star_pity) * 100.0 });
std.debug.print("FOUR STAR PITY: {}, (RATE: {d:.4}%)\n", .{ four_star_pity, getFourStarRate(four_star_pity) * 100.0 });
try session.send(protocol.CmdID.CmdDoGachaScRsp, rsp);
}

View file

@ -0,0 +1,90 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const Data = @import("../data.zig");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetBag(session: *Session, _: *const Packet, allocator: Allocator) !void {
const config = try Config.loadGameConfig(allocator, "config.json");
var generator = UidGenerator().init();
// fake item inventory
// TODO: make real one
var rsp = protocol.GetBagScRsp.init(allocator);
rsp.equipment_list = ArrayList(protocol.Equipment).init(allocator);
rsp.relic_list = ArrayList(protocol.Relic).init(allocator);
for (Data.ItemList) |tid| {
try rsp.material_list.append(.{ .tid = tid, .Num = 100 });
}
for (config.avatar_config.items) |avatarConf| {
// lc
const lc = protocol.Equipment{
.unique_id = generator.nextId(),
.tid = avatarConf.lightcone.id, // id
.is_protected = true, // lock
.level = avatarConf.lightcone.level,
.rank = avatarConf.lightcone.rank,
.promotion = avatarConf.lightcone.promotion,
.equip_avatar_id = avatarConf.id, // base avatar id
};
try rsp.equipment_list.append(lc);
// relics
for (avatarConf.relics.items) |input| {
var r = protocol.Relic{
.tid = input.id, // id
.main_affix_id = input.main_affix_id,
.unique_id = generator.nextId(),
.exp = 0,
.equip_avatar_id = avatarConf.id, // base avatar id
.is_protected = true, // lock
.level = input.level,
.sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
.reforge_sub_affix_list = ArrayList(protocol.RelicAffix).init(allocator),
};
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat1, .cnt = input.cnt1, .step = input.step1 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat2, .cnt = input.cnt2, .step = input.step2 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat3, .cnt = input.cnt3, .step = input.step3 });
try r.sub_affix_list.append(protocol.RelicAffix{ .affix_id = input.stat4, .cnt = input.cnt4, .step = input.step4 });
try rsp.relic_list.append(r);
}
}
try session.send(CmdID.CmdGetBagScRsp, rsp);
}
pub fn onUseItem(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UseItemCsReq, allocator);
var rsp = protocol.UseItemScRsp.init(allocator);
rsp.use_item_id = req.use_item_id;
rsp.use_item_count = req.use_item_count;
rsp.retcode = 0;
var sync = protocol.SyncLineupNotify.init(allocator);
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
sync.Lineup = lineup;
try session.send(CmdID.CmdSyncLineupNotify, sync);
try session.send(CmdID.CmdUseItemScRsp, rsp);
}
pub fn UidGenerator() type {
return struct {
current_id: u32,
const Self = @This();
pub fn init() Self {
return Self{ .current_id = 0 };
}
pub fn nextId(self: *Self) u32 {
self.current_id +%= 1; // Using wrapping addition
return self.current_id;
}
};
}

View file

@ -0,0 +1,59 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetCurLineupData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
try session.send(CmdID.CmdGetCurLineupDataScRsp, protocol.GetCurLineupDataScRsp{
.retcode = 0,
.lineup = lineup,
});
}
pub fn onChangeLineupLeader(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ChangeLineupLeaderCsReq, allocator);
try session.send(CmdID.CmdChangeLineupLeaderScRsp, protocol.ChangeLineupLeaderScRsp{
.slot = req.slot,
.retcode = 0,
});
}
pub fn onReplaceLineup(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ReplaceLineupCsReq, allocator);
var lineup = protocol.LineupInfo.init(allocator);
lineup.mp = 5;
lineup.max_mp = 5;
lineup.name = .{ .Const = "CastoriceSR" };
for (req.lineup_slot_list.items) |ok| {
const avatar = protocol.LineupAvatar{
.id = ok.id,
.slot = ok.slot,
.satiety = 0,
.hp = 10000,
.avatar_type = protocol.AvatarType.AVATAR_FORMAL_TYPE,
.sp_bar = .{ .sp_cur = 10000, .sp_max = 10000 },
};
try lineup.avatar_list.append(avatar);
}
var rsp = protocol.SyncLineupNotify.init(allocator);
rsp.Lineup = lineup;
try session.send(CmdID.CmdSyncLineupNotify, rsp);
try session.send(CmdID.CmdReplaceLineupScRsp, protocol.ReplaceLineupScRsp{
.retcode = 0,
});
}
pub fn onSetLineupName(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetLineupNameCsReq, allocator);
try session.send(CmdID.CmdSetLineupNameScRsp, protocol.SetLineupNameScRsp{
.index = req.index,
.name = req.name,
.retcode = 0,
});
}

View file

@ -0,0 +1,81 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const CmdID = protocol.CmdID;
pub fn onPlayerGetToken(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.PlayerGetTokenScRsp.init(allocator);
rsp.retcode = 0;
rsp.uid = 1;
try session.send(CmdID.CmdPlayerGetTokenScRsp, rsp);
}
pub fn onPlayerLogin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PlayerLoginCsReq, allocator);
var basic_info = protocol.PlayerBasicInfo.init(allocator);
basic_info.stamina = 300;
basic_info.level = 70;
basic_info.nickname = .{ .Const = "ReversedRooms" };
basic_info.world_level = 6;
basic_info.mcoin = 99999990;
basic_info.hcoin = 99999990; //Jade
basic_info.scoin = 99999990; //Money
var rsp = protocol.PlayerLoginScRsp.init(allocator);
rsp.retcode = 0;
rsp.login_random = req.login_random;
rsp.stamina = 300;
rsp.basic_info = basic_info;
try session.send(CmdID.CmdPlayerLoginScRsp, rsp);
}
pub fn onPlayerLoginFinish(session: *Session, _: *const Packet, allocator: Allocator) !void {
const content = [_]u32{
200001, 200002, 200003, 200004, 200005, 200006,
150017, 150015, 150021, 150018, 130011, 130012,
130013, 150025, 140006, 150026, 130014, 150034,
150029, 150035, 150039,
};
var package_data = protocol.ContentPackageData.init(allocator);
package_data.cur_content_id = 0;
for (content) |id| {
try package_data.content_package_list.append(protocol.ContentPackageInfo{
.content_id = id,
.status = protocol.ContentPackageStatus.ContentPackageStatus_Finished,
});
}
var noti = protocol.UnlockAvatarSkinScNotify.init(allocator);
noti.skin_id = 1100101;
try session.send(CmdID.CmdUnlockAvatarSkinScNotify, noti);
var phone = protocol.UnlockPhoneCaseScNotify.init(allocator);
phone.phone_case_id = 254000;
try session.send(CmdID.CmdUnlockPhoneCaseScNotify, phone);
try session.send(CmdID.CmdContentPackageSyncDataScNotify, protocol.ContentPackageSyncDataScNotify{
.data = package_data,
});
try session.send(CmdID.CmdPlayerLoginFinishScRsp, protocol.PlayerLoginFinishScRsp{
.retcode = 0,
});
}
pub fn onContentPackageGetData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.ContentPackageGetDataScRsp.init(allocator);
rsp.retcode = 0;
try session.send(CmdID.CmdContentPackageGetDataScRsp, rsp);
}
pub fn onSetClientPaused(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetClientPausedCsReq, allocator);
try session.send(CmdID.CmdSetClientPausedScRsp, protocol.SetClientPausedScRsp{
.retcode = 0,
.paused = req.paused,
});
}

View file

@ -0,0 +1,39 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const B64Decoder = std.base64.standard.Decoder;
pub fn onGetMail(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetMailScRsp.init(allocator);
var item_attachment = ArrayList(protocol.Item).init(allocator);
try item_attachment.appendSlice(&[_]protocol.Item{
.{ .ItemId = 1407, .Num = 1 },
});
var mail = protocol.ClientMail.init(allocator);
mail.Sender = .{ .Const = "Castorice" };
mail.Title = .{ .Const = "Readme" };
mail.IsRead = false;
mail.id = 1;
mail.Content = .{ .Const = "CastoriceSR is a free and open-source sofware\nJoin our discord: https://discord.gg/reversedrooms\nUse https://yunlisr-relic-builder.vercel.app to setup relic :Đ\n" };
mail.Time = 1723334400;
mail.ExpireTime = 17186330890;
mail.MailType = protocol.MailType.MAIL_TYPE_STAR;
mail.Attachment = .{ .ItemList_ = item_attachment };
var mail_list = ArrayList(protocol.ClientMail).init(allocator);
try mail_list.append(mail);
rsp.TotalNum = 1;
rsp.IsEnd = true;
rsp.Start = 0;
rsp.retcode = 0;
rsp.MailList = mail_list;
try session.send(CmdID.CmdGetMailScRsp, rsp);
}

View file

@ -0,0 +1,42 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const B64Decoder = std.base64.standard.Decoder;
pub fn onPlayerHeartBeat(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PlayerHeartBeatCsReq, allocator);
const lua_code =
\\local function setTextComponent(path, newText)
\\ local obj = CS.UnityEngine.GameObject.Find(path)
\\ if obj then
\\ local textComponent = obj:GetComponentInChildren(typeof(CS.RPG.Client.LocalizedText))
\\ if textComponent then
\\ textComponent.text = newText
\\ end
\\ end
\\end
\\
\\setTextComponent("UIRoot/AboveDialog/BetaHintDialog(Clone)", "<color=#ffc800>CipherSR is a free and open source software.</color>")
\\setTextComponent("VersionText", "<color=#ffc800>Visit discord.gg/reversedrooms for more info!</color>")
;
const data = protocol.ClientDownloadData{
.version = 51,
.time = @intCast(std.time.milliTimestamp()),
.data = .{ .Owned = .{
.allocator = allocator,
.str = try allocator.dupe(u8, lua_code),
} },
};
const rsp = protocol.PlayerHeartBeatScRsp{
.retcode = 0,
.client_time_ms = req.client_time_ms,
.server_time_ms = @intCast(std.time.milliTimestamp()),
.download_data = data,
};
try session.send(CmdID.CmdPlayerHeartBeatScRsp, rsp);
}

View file

@ -0,0 +1,31 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onPlayerHeartBeat(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.PlayerHeartBeatCsReq, allocator);
defer req.deinit();
const encoded = "ICAgICAgICBsb2NhbCBmdW5jdGlvbiBzZXRUZXh0Q29tcG9uZW50KHBhdGgsIG5ld1RleHQpDQogICAgICAgICAgICBsb2NhbCBvYmogPSBDUy5Vbml0eUVuZ2luZS5HYW1lT2JqZWN0LkZpbmQocGF0aCkNCiAgICAgICAgICAgIGlmIG9iaiB0aGVuDQogICAgICAgICAgICAgICAgbG9jYWwgdGV4dENvbXBvbmVudCA9IG9iajpHZXRDb21wb25lbnRJbkNoaWxkcmVuKHR5cGVvZihDUy5SUEcuQ2xpZW50LkxvY2FsaXplZFRleHQpKQ0KICAgICAgICAgICAgICAgIGlmIHRleHRDb21wb25lbnQgdGhlbg0KICAgICAgICAgICAgICAgICAgICB0ZXh0Q29tcG9uZW50LnRleHQgPSBuZXdUZXh0DQogICAgICAgICAgICAgICAgZW5kDQogICAgICAgICAgICBlbmQNCiAgICAgICAgZW5kDQogICAgICAgIA0KICAgICAgICBzZXRUZXh0Q29tcG9uZW50KCJVSVJvb3QvQWJvdmVEaWFsb2cvQmV0YUhpbnREaWFsb2coQ2xvbmUpIiwgIjxjb2xvcj0jMDNjMmZjPkNpcGhlclNSIGlzIGEgZnJlZSBhbmQgb3BlbiBzb3VyY2Ugc29mdHdhcmUuPC9jb2xvcj4iKQ0KICAgICAgICBzZXRUZXh0Q29tcG9uZW50KCJWZXJzaW9uVGV4dCIsICI8Y29sb3I9IzAzYzJmYz5WaXNpdCBkaXNjb3JkLmdnL3JldmVyc2Vkcm9vbXMgZm9yIG1vcmUgaW5mbyE8L2NvbG9yPiIpDQo=";
const dest_buf = try allocator.alloc(u8, try std.base64.standard.Decoder.calcSizeForSlice(encoded));
try std.base64.standard.Decoder.decode(dest_buf, encoded);
const managed_str = protocol.ManagedString.move(dest_buf, allocator);
const download_data = protocol.ClientDownloadData{
.version = 51,
.time = @intCast(std.time.milliTimestamp()),
.data = managed_str,
};
try session.send(CmdID.CmdPlayerHeartBeatScRsp, protocol.PlayerHeartBeatScRsp{
.retcode = 0,
.client_time_ms = req.client_time_ms,
.server_time_ms = @intCast(std.time.milliTimestamp()),
.download_data = download_data,
});
}

View file

@ -0,0 +1,65 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetMissionStatus(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetMissionStatusCsReq, allocator);
var rsp = protocol.GetMissionStatusScRsp.init(allocator);
rsp.retcode = 0;
for (req.sub_mission_id_list.items) |id| {
try rsp.SubMissionStatusList.append(protocol.Mission{ .id = id, .status = protocol.MissionStatus.MISSION_FINISH, .progress = 1 });
}
try rsp.FinishedMainMissionIdList.appendSlice(&Data.FinishedMainMissionIdList);
try rsp.CurversionFinishedMainMissionIdList.appendSlice(&Data.FinishedMainMissionIdList);
try session.send(CmdID.CmdGetMissionStatusScRsp, rsp);
}
pub fn onGetTutorialGuideStatus(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetTutorialGuideScRsp.init(allocator);
rsp.retcode = 0;
for (Data.TutorialGuideIdList) |id| {
try rsp.TutorialGuideList.append(protocol.TutorialGuide{ .id = id, .Status = protocol.TutorialStatus.TUTORIAL_FINISH });
}
try session.send(CmdID.CmdGetTutorialGuideScRsp, rsp);
}
pub fn onFinishTutorialGuideStatus(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.FinishTutorialGuideScRsp.init(allocator);
rsp.retcode = 0;
for (Data.TutorialGuideIdList) |id| {
rsp.TutorialGuide = .{ .id = id, .Status = protocol.TutorialStatus.TUTORIAL_FINISH };
}
try session.send(CmdID.CmdFinishTutorialScRsp, rsp);
}
pub fn onGetTutorialStatus(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetTutorialScRsp.init(allocator);
rsp.retcode = 0;
for (Data.FinishedTutorialIdList) |id| {
try rsp.TutorialList.append(protocol.Tutorial{ .id = id, .Status = protocol.TutorialStatus.TUTORIAL_FINISH });
}
try session.send(CmdID.CmdGetTutorialScRsp, rsp);
}
// added this to auto detect new tutorial guide id
pub fn onUnlockTutorialGuide(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UnlockTutorialGuideCsReq, allocator);
var rsp = protocol.UnlockTutorialGuideScRsp.init(allocator);
rsp.retcode = 0;
std.debug.print("UNLOCK TUTORIAL GUIDE ID: {}\n", .{req.group_id});
try session.send(CmdID.CmdUnlockTutorialGuideScRsp, rsp);
}

View file

@ -0,0 +1,17 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const Data = @import("../data.zig");
const MultiPathManager = @import("../manager/multipath_mgr.zig").MultiPathManager;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
pub fn onGetMultiPathAvatarInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var multipath = MultiPathManager.init(allocator);
const rsp = try multipath.createMultiPath(1100101);
try session.send(CmdID.CmdGetMultiPathAvatarInfoScRsp, rsp);
}

View file

@ -0,0 +1,36 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const OwnedPet = [_]u32{ 251001, 251002 };
pub fn onGetPetData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPetDataScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_pet_id = 1002;
try rsp.unlocked_pet_id.appendSlice(&OwnedPet);
try session.send(CmdID.CmdGetPetDataScRsp, rsp);
}
pub fn onRecallPet(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.RecallPetScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_pet_id = 1002;
try session.send(CmdID.CmdRecallPetScRsp, rsp);
}
pub fn onSummonPet(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.CurPetChangedScNotify.init(allocator);
rsp.cur_pet_id = 1002;
try session.send(CmdID.CmdCurPetChangedScNotify, rsp);
try session.send(CmdID.CmdSummonPetScRsp, protocol.SummonPetScRsp{
.retcode = 0,
.cur_pet_id = rsp.cur_pet_id,
});
}

View file

@ -0,0 +1,108 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const UidGenerator = @import("item.zig").UidGenerator;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
// can change these id here for initial display
const SupportAvatar = [_]u32{
1407, 1403, 1402,
};
const ListAvatar = [_]u32{
1401, 1001, 1225, 1317, 1222,
};
pub fn onGetPhoneData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPhoneDataScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_chat_bubble = 0;
rsp.cur_phone_theme = 0;
rsp.cur_phone_case = 254000;
try rsp.owned_chat_bubbles.appendSlice(&Data.OwnedChatBubbles);
try rsp.owned_phone_themes.appendSlice(&Data.OwnedPhoneThemes);
try rsp.owned_phone_cases.appendSlice(&Data.OwnedPhoneCases);
try session.send(CmdID.CmdGetPhoneDataScRsp, rsp);
}
pub fn onSelectPhoneTheme(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SelectPhoneThemeCsReq, allocator);
var rsp = protocol.SelectPhoneThemeScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_phone_theme = req.theme_id;
try session.send(CmdID.CmdSelectPhoneThemeScRsp, rsp);
}
pub fn onSelectChatBubble(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SelectChatBubbleCsReq, allocator);
var rsp = protocol.SelectChatBubbleScRsp.init(allocator);
rsp.retcode = 0;
rsp.cur_chat_bubble = req.bubble_id;
try session.send(CmdID.CmdSelectChatBubbleScRsp, rsp);
}
pub fn onGetPlayerBoardData(session: *Session, _: *const Packet, allocator: Allocator) !void {
var rsp = protocol.GetPlayerBoardDataScRsp.init(allocator);
var generator = UidGenerator().init();
var display_list = protocol.DisplayAvatarVec.init(allocator);
display_list.is_display = true;
rsp.retcode = 0;
rsp.OLDMJONBJOM = 253000;
rsp.signature = .{ .Const = "Visit discord.gg/reversedrooms for more info!" };
try rsp.assist_avatar_id_list.appendSlice(&SupportAvatar);
for (ListAvatar) |id| {
var A_list = protocol.DisplayAvatarData.init(allocator);
A_list.avatar_id = id;
A_list.pos = generator.nextId();
try display_list.display_avatar_list.append(A_list);
}
rsp.display_avatar_vec = display_list;
for (Data.OwnedHeadIcon) |head_id| {
const head_icon = protocol.HeadIconData{
.id = head_id,
};
try rsp.unlocked_head_icon_list.append(head_icon);
}
for (Data.OwnedPersonalCardSkin) |card_skin_id| {
try rsp.KKNJHENMGPK.append(card_skin_id);
}
try session.send(CmdID.CmdGetPlayerBoardDataScRsp, rsp);
}
pub fn onSetAssistAvatar(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetAssistAvatarCsReq, allocator);
var rsp = protocol.SetAssistAvatarScRsp.init(allocator);
rsp.retcode = 0;
rsp.avatar_id = req.avatar_id;
rsp.avatar_id_list = req.avatar_id_list;
try session.send(CmdID.CmdSetAssistAvatarScRsp, rsp);
}
pub fn onSetDisplayAvatar(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetDisplayAvatarCsReq, allocator);
var rsp = protocol.SetDisplayAvatarScRsp.init(allocator);
rsp.retcode = 0;
rsp.display_avatar_list = req.display_avatar_list;
try session.send(CmdID.CmdSetDisplayAvatarScRsp, rsp);
}
pub fn onSetSignature(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetSignatureCsReq, allocator);
var rsp = protocol.SetSignatureScRsp.init(allocator);
rsp.retcode = 0;
rsp.signature = req.signature;
try session.send(CmdID.CmdSetSignatureScRsp, rsp);
}
pub fn onSetGameplayBirthday(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetGameplayBirthdayCsReq, allocator);
var rsp = protocol.SetGameplayBirthdayScRsp.init(allocator);
rsp.retcode = 0;
rsp.birthday = req.birthday;
try session.send(CmdID.CmdSetGameplayBirthdayScRsp, rsp);
}
pub fn onSetHeadIcon(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetHeadIconCsReq, allocator);
var rsp = protocol.SetHeadIconScRsp.init(allocator);
rsp.retcode = 0;
rsp.current_head_icon_id = req.id;
try session.send(CmdID.CmdSetHeadIconScRsp, rsp);
}

View file

@ -0,0 +1,146 @@
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const std = @import("std");
const ResConfig = struct {
planeID: u32,
props: ArrayList(Props),
monsters: ArrayList(Monsters),
teleports: ArrayList(Teleports),
};
const Vector = struct {
x: i32,
y: i32,
z: i32,
};
const Teleports = struct {
anchorId: u32,
groupId: u32,
instId: u32,
pos: Vector,
rot: Vector,
teleportId: u32,
};
const Monsters = struct {
groupId: u32,
instId: u32,
eventId: u32,
pos: Vector,
rot: Vector,
monsterId: u32,
};
const Props = struct {
groupId: u32,
instId: u32,
propState: u32,
pos: Vector,
rot: Vector,
propId: u32,
};
pub const SceneConfig = struct {
scene_config: ArrayList(ResConfig),
};
pub fn anchorLoader(allocator: Allocator, filename: []const u8) !SceneConfig {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
const buffer = try file.readToEndAlloc(allocator, file_size);
defer allocator.free(buffer);
var json_tree = try std.json.parseFromSlice(std.json.Value, allocator, buffer, .{});
defer json_tree.deinit();
const root = json_tree.value;
const config: SceneConfig = try parseAnchor(root, allocator);
return config;
}
fn parseAnchor(root: anytype, allocator: Allocator) !SceneConfig {
var res_config = ArrayList(ResConfig).init(allocator);
for (root.object.get("scene_config").?.array.items) |res_json| {
var res = ResConfig{
.planeID = @intCast(res_json.object.get("planeID").?.integer),
.props = ArrayList(Props).init(allocator),
.monsters = ArrayList(Monsters).init(allocator),
.teleports = ArrayList(Teleports).init(allocator),
};
for (res_json.object.get("props").?.array.items) |scene_prop| {
var prop = Props{
.groupId = @intCast(scene_prop.object.get("groupId").?.integer),
.instId = @intCast(scene_prop.object.get("instId").?.integer),
.propState = @intCast(scene_prop.object.get("propState").?.integer),
.pos = undefined,
.rot = undefined,
.propId = @intCast(scene_prop.object.get("propId").?.integer),
};
const pos_json = scene_prop.object.get("pos").?;
prop.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = scene_prop.object.get("rot").?;
prop.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try res.props.append(prop);
}
for (res_json.object.get("monsters").?.array.items) |monster_json| {
var monster = Monsters{
.groupId = @intCast(monster_json.object.get("groupId").?.integer),
.instId = @intCast(monster_json.object.get("instId").?.integer),
.eventId = @intCast(monster_json.object.get("eventId").?.integer),
.monsterId = @intCast(monster_json.object.get("monsterId").?.integer),
.pos = undefined,
.rot = undefined,
};
const pos_json = monster_json.object.get("pos").?;
monster.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = monster_json.object.get("rot").?;
monster.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try res.monsters.append(monster);
}
for (res_json.object.get("teleports").?.array.items) |teleport_json| {
var teleport = Teleports{
.anchorId = @intCast(teleport_json.object.get("anchorId").?.integer),
.groupId = @intCast(teleport_json.object.get("groupId").?.integer),
.instId = @intCast(teleport_json.object.get("instId").?.integer),
.teleportId = @intCast(teleport_json.object.get("teleportId").?.integer),
.pos = undefined,
.rot = undefined,
};
const pos_json = teleport_json.object.get("pos").?;
teleport.pos = Vector{
.x = @intCast(pos_json.object.get("x").?.integer),
.y = @intCast(pos_json.object.get("y").?.integer),
.z = @intCast(pos_json.object.get("z").?.integer),
};
const rot_json = teleport_json.object.get("rot").?;
teleport.rot = Vector{
.x = @intCast(rot_json.object.get("x").?.integer),
.y = @intCast(rot_json.object.get("y").?.integer),
.z = @intCast(rot_json.object.get("z").?.integer),
};
try res.teleports.append(teleport);
}
try res_config.append(res);
}
return SceneConfig{
.scene_config = res_config,
};
}

View file

@ -0,0 +1,235 @@
const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Config = @import("config.zig");
const Data = @import("../data.zig");
const Res_config = @import("res_config.zig");
const LineupManager = @import("../manager/lineup_mgr.zig").LineupManager;
const SceneManager = @import("../manager/scene_mgr.zig").SceneManager;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;
const log = std.log.scoped(.scene_service);
pub fn onGetCurSceneInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
var scene_manager = SceneManager.init(allocator);
const scene_info = try scene_manager.createScene(20432, 20432001, 2043201, 1213);
try session.send(CmdID.CmdGetCurSceneInfoScRsp, protocol.GetCurSceneInfoScRsp{
.scene = scene_info,
.retcode = 0,
});
}
pub fn onSceneEntityMove(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SceneEntityMoveCsReq, allocator);
for (req.entity_motion_list.items) |entity_motion| {
if (entity_motion.motion) |motion| {
if (entity_motion.entity_id > 99999 or entity_motion.entity_id == 0)
log.debug("[POSITION] entity_id: {}, motion: {}", .{ entity_motion.entity_id, motion });
}
}
try session.send(CmdID.CmdSceneEntityMoveScRsp, protocol.SceneEntityMoveScRsp{
.retcode = 0,
.entity_motion_list = req.entity_motion_list,
.download_data = null,
});
}
pub fn onEnterScene(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const entrance_config = try Config.loadMapEntranceConfig(allocator, "resources/MapEntrance.json");
const req = try packet.getProto(protocol.EnterSceneCsReq, allocator);
var lineup_mgr = LineupManager.init(allocator);
const lineup = try lineup_mgr.createLineup();
var scene_manager = SceneManager.init(allocator);
var floorID: u32 = 0;
var planeID: u32 = 0;
var teleportID: u32 = 0;
for (entrance_config.map_entrance_config.items) |entrConf| {
if (entrConf.id == req.entry_id) {
floorID = entrConf.floor_id;
planeID = entrConf.plane_id;
teleportID = req.teleport_id;
}
}
const scene_info = try scene_manager.createScene(planeID, floorID, req.entry_id, teleportID);
std.debug.print("ENTER SCENE ENTRY ID: {}, PLANE ID: {}, FLOOR ID: {}, TELEPORT ID: {}\n", .{ req.entry_id, planeID, floorID, teleportID });
try session.send(CmdID.CmdEnterSceneByServerScNotify, protocol.EnterSceneByServerScNotify{
.lineup = lineup,
.reason = protocol.EnterSceneReason.ENTER_SCENE_REASON_NONE,
.scene = scene_info,
});
try session.send(CmdID.CmdEnterSceneScRsp, protocol.EnterSceneScRsp{
.retcode = 0,
.game_story_line_id = req.game_story_line_id,
.is_close_map = req.is_close_map,
.content_id = req.content_id,
.is_over_map = true,
});
}
//TODO FIX CURRENT SCENE MAP DISPLAY
pub fn onGetSceneMapInfo(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetSceneMapInfoCsReq, allocator);
const res_config = try Res_config.anchorLoader(allocator, "resources/res.json");
const ranges = [_][2]usize{
.{ 0, 101 },
.{ 10000, 10051 },
.{ 20000, 20001 },
.{ 30000, 30020 },
};
const chest_list = &[_]protocol.ChestInfo{
.{ .chest_type = protocol.ChestType.MAP_INFO_CHEST_TYPE_NORMAL },
.{ .chest_type = protocol.ChestType.MAP_INFO_CHEST_TYPE_CHALLENGE },
.{ .chest_type = protocol.ChestType.MAP_INFO_CHEST_TYPE_PUZZLE },
};
std.debug.print("SCENE MAP FLOOR ID REQ: {}\n", .{req.floor_id_list});
for (req.floor_id_list.items) |floor_id| {
var rsp = protocol.GetSceneMapInfoScRsp.init(allocator);
rsp.retcode = 0;
rsp.content_id = req.content_id;
rsp.entry_story_line_id = req.entry_story_line_id;
var map_info = protocol.SceneMapInfo.init(allocator);
try map_info.chest_list.appendSlice(chest_list);
map_info.entry_id = @intCast(floor_id);
map_info.floor_id = @intCast(floor_id);
map_info.cur_map_entry_id = @intCast(floor_id);
for (res_config.scene_config.items) |sceneConf| {
if (sceneConf.planeID != floor_id / 1000) continue;
try map_info.unlock_teleport_list.ensureUnusedCapacity(sceneConf.teleports.items.len);
try map_info.maze_prop_list.ensureUnusedCapacity(sceneConf.props.items.len);
try map_info.maze_group_list.ensureUnusedCapacity(sceneConf.props.items.len);
for (ranges) |range| {
for (range[0]..range[1]) |i| {
try map_info.lighten_section_list.append(@intCast(i));
}
}
for (sceneConf.teleports.items) |teleConf| {
try map_info.unlock_teleport_list.append(@intCast(teleConf.teleportId));
}
for (sceneConf.props.items) |propConf| {
try map_info.maze_prop_list.append(protocol.MazePropState{
.group_id = propConf.groupId,
.config_id = propConf.instId,
.state = propConf.propState,
});
try map_info.LMNGAHFNAON.append(protocol.OFCAIGDHPOH{
.group_id = propConf.groupId,
.config_id = propConf.instId,
.state = propConf.propState,
});
try map_info.maze_group_list.append(protocol.MazeGroup{
.NOBKEONAKLE = std.ArrayList(u32).init(allocator),
.group_id = propConf.groupId,
});
}
}
try rsp.scene_map_info.append(map_info);
try session.send(CmdID.CmdGetSceneMapInfoScRsp, rsp);
}
}
pub fn onGetUnlockTeleport(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetUnlockTeleportCsReq, allocator);
std.debug.print("UNLOCK TELEPORT ENTRY ID REQ: {}\n", .{req.entry_id_list});
var rsp = protocol.GetUnlockTeleportScRsp.init(allocator);
const res_config = try Res_config.anchorLoader(allocator, "resources/res.json");
for (res_config.scene_config.items) |sceneCof| {
for (sceneCof.teleports.items) |tp| {
try rsp.unlock_teleport_list.append(tp.teleportId);
}
}
rsp.retcode = 0;
try session.send(CmdID.CmdGetUnlockTeleportScRsp, rsp);
}
pub fn onEnterSection(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.EnterSectionCsReq, allocator);
var rsp = protocol.EnterSectionScRsp.init(allocator);
rsp.retcode = 0;
std.debug.print("ENTER SECTION Id: {}\n", .{req.section_id});
try session.send(CmdID.CmdEnterSectionScRsp, rsp);
}
pub fn onSceneEntityTeleport(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SceneEntityTeleportCsReq, allocator);
var rsp = protocol.SceneEntityTeleportScRsp.init(allocator);
rsp.retcode = 0;
rsp.entity_motion = req.entity_motion;
std.debug.print("SCENE ENTITY TP ENTRY ID: {}\n", .{req.entry_id});
try session.send(CmdID.CmdSceneEntityTeleportScRsp, rsp);
}
pub fn onGetFirstTalkNpc(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetFirstTalkNpcCsReq, allocator);
var rsp = protocol.GetFirstTalkNpcScRsp.init(allocator);
rsp.retcode = 0;
for (req.npc_id_list.items) |id| {
try rsp.npc_meet_status_list.append(protocol.FirstNpcTalkInfo{ .npc_id = id, .is_meet = true });
}
try session.send(CmdID.CmdGetFirstTalkNpcScRsp, rsp);
}
pub fn onGetFirstTalkByPerformanceNp(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetFirstTalkByPerformanceNpcCsReq, allocator);
var rsp = protocol.GetFirstTalkByPerformanceNpcScRsp.init(allocator);
rsp.retcode = 0;
for (req.performance_id_list.items) |id| {
try rsp.npc_meet_status_list.append(
protocol.NpcMeetByPerformanceStatus{ .performance_id = id, .is_meet = true },
);
}
try session.send(CmdID.CmdGetFirstTalkByPerformanceNpcScRsp, rsp);
}
pub fn onGetNpcTakenReward(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.GetNpcTakenRewardCsReq, allocator);
var rsp = protocol.GetNpcTakenRewardScRsp.init(allocator);
const EventList = [_]u32{ 2136, 2134 };
rsp.retcode = 0;
rsp.npc_id = req.npc_id;
try rsp.talk_event_list.appendSlice(&EventList);
try session.send(CmdID.CmdGetNpcTakenRewardScRsp, rsp);
}
pub fn onUpdateGroupProperty(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.UpdateGroupPropertyCsReq, allocator);
var rsp = protocol.UpdateGroupPropertyScRsp.init(allocator);
rsp.retcode = 0;
rsp.floor_id = req.floor_id;
rsp.group_id = req.group_id;
rsp.dimension_id = req.dimension_id;
rsp.JAIBIEEKHEG = req.JAIBIEEKHEG;
try session.send(CmdID.CmdUpdateGroupPropertyScRsp, rsp);
}
pub fn onChangePropTimeline(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.ChangePropTimelineInfoCsReq, allocator);
try session.send(CmdID.CmdChangePropTimelineInfoScRsp, protocol.ChangePropTimelineInfoScRsp{
.retcode = 0,
.prop_entity_id = req.prop_entity_id,
});
}
pub fn onDeactivateFarmElement(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.DeactivateFarmElementCsReq, allocator);
std.debug.print("DEACTIVATE FARM ELEMENT ENTITY ID: {}\n", .{req.entity_id});
try session.send(CmdID.CmdDeactivateFarmElementScRsp, protocol.DeactivateFarmElementScRsp{
.retcode = 0,
.entity_id = req.entity_id,
});
}
pub fn onSetGroupCustomSaveData(session: *Session, packet: *const Packet, allocator: Allocator) !void {
const req = try packet.getProto(protocol.SetGroupCustomSaveDataCsReq, allocator);
switch (req.AAMHHECOCOI) {
.Owned => |val| {
std.debug.print("CUSTOM SAVE DATA REQ: {s}\n", .{val.str});
},
.Const => |val| {
std.debug.print("CUSTOM SAVE DATA REQ: {s}\n", .{val});
},
.Empty => {
std.debug.print("CUSTOM SAVE DATA REQ: <empty string>\n", .{});
},
}
try session.send(CmdID.CmdSetGroupCustomSaveDataScRsp, protocol.SetGroupCustomSaveDataScRsp{
.retcode = 0,
.group_id = req.group_id,
.entry_id = req.entry_id,
});
}

18
hotfix.json Normal file
View file

@ -0,0 +1,18 @@
{
"OSBETAWin3.2.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9961450_404865fc6856_fbf8b526e6b08c",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9984498_2b39fa0086e4_1f081bc40d1782",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253",
"ifix_version": "0",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9961575_227d4559d8e3_cd27ed7ca2bbfd",
"lua_version": ""
},
"OSBETAWin3.2.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_10025177_f10d2880371c_719811bc00424c",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_10037027_be14d3e01696_5aa1cd3547cbdb",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4",
"ifix_version": "0",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_10025298_1dc728a6c8ee_1128f068b506d4",
"lua_version": ""
}
}

18
protocol/build.zig Normal file
View file

@ -0,0 +1,18 @@
const std = @import("std");
const protobuf = @import("protobuf");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const protobuf_dep = b.dependency("protobuf", .{
.optimize = optimize,
.target = target,
});
const protocol = b.addModule("protocol", .{
.root_source_file = b.path("src/root.zig"),
});
//
protocol.addImport("protobuf", protobuf_dep.module("protobuf"));
}

17
protocol/build.zig.zon Normal file
View file

@ -0,0 +1,17 @@
.{
.name = "protocol",
.version = "0.0.0",
.dependencies = .{
.protobuf = .{
//.url = "https://github.com/Arwalk/zig-protobuf/archive/7c49ed66e029c9c7e6253b3d6d256118745550a4.tar.gz",
.url = "git+https://github.com/Arwalk/zig-protobuf?ref=v2.0.0#42ccf142e5d6b7de886b766af7d287021c561dfd",
.hash = "122063ee7ff32a3c1aefd91a46a9fc23df0571949c3a02e2f44d39afbad0b53018a3",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

49028
protocol/src/protocol.pb.zig Normal file

File diff suppressed because it is too large Load diff

1894
protocol/src/root.zig Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6685
resources/MapEntrance.json Normal file

File diff suppressed because it is too large Load diff

4277
resources/MazePlane.json Normal file

File diff suppressed because it is too large Load diff

466085
resources/res.json Normal file

File diff suppressed because it is too large Load diff