Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ add_executable(xiloader
src/network.cpp
src/network.h
src/polcore.h
src/trust_token.cpp
src/trust_token.h
${CMAKE_CURRENT_BINARY_DIR}/src/xiloader.rc
xiloader.manifest
)
Expand Down
32 changes: 31 additions & 1 deletion src/command_handler.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
===========================================================================

Copyright (c) 2025 LandSandBoat Dev Teams
Copyright (c) 2026 LandSandBoat Dev Teams

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -38,11 +38,14 @@ namespace globals
extern char* g_CharacterList;
extern bool g_IsRunning;
extern bool g_FirstLogin;
extern std::string g_TrustToken;
extern bool g_TrustThisComputer;
} // namespace globals

#include "defines.h"
#include "helpers.h"
#include "network.h"
#include "trust_token.h"

#include <nlohmann/json.hpp>
#include <qrcodegen.hpp>
Expand Down Expand Up @@ -81,6 +84,19 @@ bool handleLoginCommand(int8_t command, json& login_reply_json, uint32_t& accoun

xiloader::console::output(xiloader::color::success, "Successfully logged in as %s!", globals::g_Username.c_str());

auto maybeTrustToken = jsonGet<std::string>(login_reply_json, "trust_token");
if (maybeTrustToken.has_value() && !maybeTrustToken.value().empty())
{
// Use server-provided expiry; fall back to 30 days if absent
int64_t expires = jsonGet<int64_t>(login_reply_json, "trust_expires")
.value_or(static_cast<int64_t>(time(nullptr)) + (30 * 24 * 60 * 60));
saveTrustToken(globals::g_ServerAddress, globals::g_Username,
maybeTrustToken.value(), expires);
int64_t days = (expires - static_cast<int64_t>(time(nullptr))) / (24 * 60 * 60);
xiloader::console::output(xiloader::color::info, "This computer is now trusted for %lld day%s.",
days, days == 1 ? "" : "s");
}

shutdown(sock, SD_BOTH);

return true;
Expand Down Expand Up @@ -133,6 +149,18 @@ bool handleLoginCommand(int8_t command, json& login_reply_json, uint32_t& accoun
return false;
}

// LOGIN_ERROR_TRUST_TOKEN_INVALID
case 0x0013:
{
removeTrustToken(globals::g_ServerAddress, globals::g_Username);
xiloader::console::output(xiloader::color::error, "==========================================================");
xiloader::console::output(xiloader::color::error, "Trust token rejected! It has been cleared from this computer.");
xiloader::console::output(xiloader::color::error, "Please log in again and enter your OTP code.");
xiloader::console::output(xiloader::color::error, "==========================================================");

return false;
}

// LOGIN_SUCCESS_CREATE_TOTP
case 0x0010:
{
Expand Down Expand Up @@ -194,6 +222,8 @@ bool handleLoginCommand(int8_t command, json& login_reply_json, uint32_t& accoun
xiloader::console::output(xiloader::color::info, "Your TOTP has been removed.");
xiloader::console::output(xiloader::color::info, "You no longer need to use an OTP code to login.");

removeTrustToken(globals::g_ServerAddress, globals::g_Username);

return false;
}

Expand Down
9 changes: 8 additions & 1 deletion src/helpers.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
===========================================================================

Copyright (c) 2022 LandSandBoat Dev Teams
Copyright (c) 2026 LandSandBoat Dev Teams

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -20,6 +20,13 @@ along with this program. If not, see http://www.gnu.org/licenses/
*/
#pragma once

#ifndef NOMINMAX
#define NOMINMAX 1
#endif

#include <winsock2.h>
#include <windows.h>

#include <cstddef>
#include <filesystem>
#include <fstream>
Expand Down
44 changes: 27 additions & 17 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
===========================================================================

Copyright (c) 2010-2015 Darkstar Dev Teams
Copyright (c) 2021-2022 LandSandBoat Dev Teams
Copyright (c) 2021-2026 LandSandBoat Dev Teams

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -45,19 +45,21 @@ using json = nlohmann::json;
/* Global Variables */
namespace globals
{
xiloader::Language g_Language = xiloader::Language::English; // The language of the loader to be used for polcore.
std::string g_ServerAddress = "127.0.0.1"; // The server address to connect to.
uint16_t g_ServerPort = 51220; // The server lobby server port to connect to.
uint16_t g_LoginDataPort = 54230; // Login server data port to connect to
uint16_t g_LoginViewPort = 54001; // Login view port to connect to
uint16_t g_LoginAuthPort = 54231; // Login auth port to connect to
std::string g_Username = ""; // The username being logged in with.
std::string g_Password = ""; // The password being logged in with.
std::string g_OtpCode = ""; // The OTP code the user input
char g_SessionHash[16] = {}; // Session hash sent from auth
std::string g_Email = ""; // Email, currently unused
std::array<uint8_t, 3> g_VersionNumber = { 2, 0, 1 }; // xiloader version number sent to auth server. Must be x.x.x with single characters for 'x'. Remember to also change in xiloader.rc.in
bool g_FirstLogin = false; // set to true when --user --pass are both set to allow for autologin
xiloader::Language g_Language = xiloader::Language::English; // The language of the loader to be used for polcore.
std::string g_ServerAddress = "127.0.0.1"; // The server address to connect to.
uint16_t g_ServerPort = 51220; // The server lobby server port to connect to.
uint16_t g_LoginDataPort = 54230; // Login server data port to connect to
uint16_t g_LoginViewPort = 54001; // Login view port to connect to
uint16_t g_LoginAuthPort = 54231; // Login auth port to connect to
std::string g_Username = ""; // The username being logged in with.
std::string g_Password = ""; // The password being logged in with.
std::string g_OtpCode = ""; // The OTP code the user input
char g_SessionHash[16] = {}; // Session hash sent from auth
std::string g_Email = ""; // Email, currently unused
std::array<uint8_t, 3> g_VersionNumber = { 2, 1, 0 }; // xiloader version number sent to auth server. Must be x.x.x with single characters for 'x'. Remember to also change in xiloader.rc.in
bool g_FirstLogin = false; // set to true when --user --pass are both set to allow for autologin
std::string g_TrustToken = ""; // trust token loaded from disk or received from server
bool g_TrustThisComputer = false; // user checkbox / CLI flag for "trust this computer"

char* g_CharacterList = NULL; // Pointer to the character list data being sent from the server.
bool g_IsRunning = false; // Flag to determine if the network threads should hault.
Expand Down Expand Up @@ -467,6 +469,11 @@ int __cdecl main(int argc, char* argv[])
.help("(optional) Determines whether or not to hide the console window after FFXI starts.")
.append();

args.add_argument("--trust")
.implicit_value(true)
.help("(optional) Trust this computer for 30 days, skipping 2FA on subsequent logins.")
.append();

args.add_argument("--json", "--json-file")
.help("(optional) The json file to load arguments in from")
.append();
Expand Down Expand Up @@ -531,6 +538,8 @@ int __cdecl main(int argc, char* argv[])

globals::g_Hide = args.is_used("--hide") ? args.get<bool>("--hide") : globals::g_Hide;

globals::g_TrustThisComputer = args.is_used("--trust") ? args.get<bool>("--trust") : globals::g_TrustThisComputer;

bool readInJsonArgs = false;
if (!jsonFilename.empty())
{
Expand Down Expand Up @@ -589,8 +598,9 @@ int __cdecl main(int argc, char* argv[])
globals::g_OtpCode = jsonGet<std::string>(jsonData, "otp").value_or(globals::g_OtpCode);
globals::g_Email = jsonGet<std::string>(jsonData, "email").value_or(globals::g_Email);

bUseHairpinFix = jsonGet<bool>(jsonData, "hairpin").value_or(bUseHairpinFix);
globals::g_Hide = jsonGet<bool>(jsonData, "hide").value_or(globals::g_Hide);
bUseHairpinFix = jsonGet<bool>(jsonData, "hairpin").value_or(bUseHairpinFix);
globals::g_Hide = jsonGet<bool>(jsonData, "hide").value_or(globals::g_Hide);
globals::g_TrustThisComputer = jsonGet<bool>(jsonData, "trust_this_computer").value_or(globals::g_TrustThisComputer);

std::string language = jsonGet<std::string>(jsonData, "language").value_or({});

Expand Down Expand Up @@ -753,7 +763,7 @@ int __cdecl main(int argc, char* argv[])
{
/* Invoke the setup functions for polcore.. */
// Create string for the login view port
std::string polcorecmd = " /game eAZcFcB -net 3 -port " + globals::g_LoginViewPort;
std::string polcorecmd = " /game eAZcFcB -net 3 -port " + std::to_string(globals::g_LoginViewPort);
// Cast to an LPSTR
LPSTR cmd = const_cast<char*>(polcorecmd.c_str());
polcore->SetAreaCode(globals::g_Language);
Expand Down
Loading