diff --git a/.gitignore b/.gitignore
index e57e422..4d95ea9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+sqpatch/target/
+
# User-specific files
*.rsuser
*.suo
diff --git a/FFXIVLauncher/FFXIVLauncher.vcxproj b/FFXIVLauncher/FFXIVLauncher.vcxproj
index 1713030..2c12ebc 100644
--- a/FFXIVLauncher/FFXIVLauncher.vcxproj
+++ b/FFXIVLauncher/FFXIVLauncher.vcxproj
@@ -183,6 +183,9 @@
+
+
+
diff --git a/FFXIVLauncher/FFXIVLauncher.vcxproj.filters b/FFXIVLauncher/FFXIVLauncher.vcxproj.filters
index adc6ebf..28303c8 100644
--- a/FFXIVLauncher/FFXIVLauncher.vcxproj.filters
+++ b/FFXIVLauncher/FFXIVLauncher.vcxproj.filters
@@ -96,4 +96,9 @@
Resource Files
+
+
+ Resource Files
+
+
\ No newline at end of file
diff --git a/FFXIVLauncher/Http.cpp b/FFXIVLauncher/Http.cpp
index feb156e..7cf28e7 100644
--- a/FFXIVLauncher/Http.cpp
+++ b/FFXIVLauncher/Http.cpp
@@ -1,9 +1,34 @@
#include "Common.h"
#include "Http.h"
+#include
+#include
template using ComPtr = ATL::CComPtr;
-static const char *const USER_AGENT = "SQEXAuthor/2.0.0(Windows 6.2; ja-jp; 15c5fd77b2)";
+static char ToHex(uint8_t in)
+{
+ assert(in < 16);
+ if (in < 10) return '0' + in;
+ return 'a' + in - 10;
+}
+
+static std::string CalcUserAgent()
+{
+ // Just use the MAC address
+ UUID uuid;
+ UuidCreateSequential(&uuid);
+
+ char mac_string[11] = { 0 };
+ for (int i = 0; i < 5; ++i)
+ {
+ mac_string[i * 2] = ToHex(uuid.Data4[i + 3] >> 4);
+ mac_string[i * 2 + 1] = ToHex(uuid.Data4[i + 3] & 0xF);
+ }
+
+ return std::string("SQEXAuthor/2.0.0(Windows 6.2; ja-jp; ") + mac_string + ")";
+}
+
+static const std::string USER_AGENT = CalcUserAgent();
static const char *const VERBS[2] =
{
@@ -48,13 +73,22 @@ HRESULT DoRequest(const Request &req, Response &response)
goto cleanup;
}
- hI = InternetOpenA(USER_AGENT, INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0);
+ hI = InternetOpenA(USER_AGENT.c_str(), INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0);
if (hI == nullptr) goto fail;
hCon = InternetConnectA(hI, url.host.c_str(), 443, nullptr, nullptr, INTERNET_SERVICE_HTTP, 0, 0);
if (hCon == nullptr) goto fail;
- hReq = HttpOpenRequestA(hCon, VERBS[(int)req.method], url.path.c_str(), "HTTP/1.1", nullptr, nullptr, INTERNET_FLAG_SECURE, 0);
+ hReq = HttpOpenRequestA(
+ hCon,
+ VERBS[(int)req.method],
+ url.path.c_str(),
+ "HTTP/1.1",
+ nullptr,
+ nullptr,
+ INTERNET_FLAG_SECURE | INTERNET_FLAG_RELOAD,
+ 0
+ );
if (hReq == nullptr) goto fail;
if (req.headers.size() > 0)
diff --git a/FFXIVLauncher/Login.cpp b/FFXIVLauncher/Login.cpp
index a5336e7..aa488a0 100644
--- a/FFXIVLauncher/Login.cpp
+++ b/FFXIVLauncher/Login.cpp
@@ -274,7 +274,15 @@ static LoginResult GetRealSID(const std::string & sid, std::string & result)
bool IsLobbyServerReady()
{
- Request req = { Method::GET, ARRSTATUS_URL };
+ Request req =
+ {
+ Method::GET,
+ ARRSTATUS_URL,
+ {
+ {"Referer", "https://arrstatus.com/"},
+ {"Cookie", "status=1"},
+ }
+ };
Response resp;
auto hr = DoRequest(req, resp);
diff --git a/FFXIVLauncher/PatchFileNotes.txt b/FFXIVLauncher/PatchFileNotes.txt
new file mode 100644
index 0000000..4d7a195
--- /dev/null
+++ b/FFXIVLauncher/PatchFileNotes.txt
@@ -0,0 +1,41 @@
+Very very incomplete notes, the ramblings of an insane girl who wants to make her own patcher for some godsforsaken reason
+
+# Patch file
+
+# HIST Entry
+
+# APLY Entry
+Entry length : 4 bytes (big endian)
+Signature : 4 bytes "APLY"
+??? : 4 bytes (big endian)
+Entry data : [Entry length] bytes
+
+# SQPK Entry
+Container length : 4 bytes (big endian)
+Signature : 4 bytes "SQPK"
+Container length : 4 bytes (big endian) (yes it seems to be duplicated; data integrity reasons?)
+Container data : [Container length] bytes
+
+All containers seem to start with a 2 byte magic marker that determines
+what kind of data is stored in there.
+
+Types:
+- "T" (TBD)
+- "X" (TBD)
+- "FA" (Raw file record?)
+
+# Raw file record?
+
+Type marker? : 2 bytes "FA"
+??? : 14 bytes (usually all 0 but not always? has nothing to do with padding len)
+Output file len : 4 bytes (big endian)
+File name len : 4 bytes (big endian)
+??? : 4 bytes (always 0?)
+File name : [File name len] bytes
+??? : 4 bytes (little endian?) (appears to always be 0x10)
+??? : 4 bytes (always 0?)
+Data len 1 : 4 bytes (little endian) (is the array len if compressed, ??? when uncompressed)
+Data len 2 : 4 bytes (little endian) (output length, and array len if compressed)
+Patch data : [File data len] bytes
+Padding : ??????? bytes (whyyyyy)
+
diff --git a/FFXIVLauncher/Resource.aps b/FFXIVLauncher/Resource.aps
index 64d5890..175a6a5 100644
Binary files a/FFXIVLauncher/Resource.aps and b/FFXIVLauncher/Resource.aps differ
diff --git a/FFXIVLauncher/Resource.rc b/FFXIVLauncher/Resource.rc
index 210c7af..f7a43fb 100644
Binary files a/FFXIVLauncher/Resource.rc and b/FFXIVLauncher/Resource.rc differ
diff --git a/FFXIVLauncher/StatusWait.cpp b/FFXIVLauncher/StatusWait.cpp
index b9e5e41..8d817d4 100644
--- a/FFXIVLauncher/StatusWait.cpp
+++ b/FFXIVLauncher/StatusWait.cpp
@@ -66,6 +66,6 @@ static void InitializeUI(HWND dialog) noexcept
auto appIcon = LoadIconW(GetModuleHandleW(nullptr), MAKEINTRESOURCE(IDI_APPICON));
SendMessageW(dialog, WM_SETICON, ICON_BIG, (LPARAM)appIcon);
- SetTimer(dialog, 69, 15'000, nullptr);
+ SetTimer(dialog, /*id=*/69, 20'000/*ms*/, nullptr);
SendDlgItemMessageW(dialog, IDC_PROGRESS1, PBM_SETMARQUEE, TRUE, 1);
}
diff --git a/sqpatch/Cargo.lock b/sqpatch/Cargo.lock
new file mode 100644
index 0000000..9004ea8
--- /dev/null
+++ b/sqpatch/Cargo.lock
@@ -0,0 +1,14 @@
+[[package]]
+name = "byteorder"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "sqpatch"
+version = "0.1.0"
+dependencies = [
+ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[metadata]
+"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
diff --git a/sqpatch/Cargo.toml b/sqpatch/Cargo.toml
new file mode 100644
index 0000000..a5495a5
--- /dev/null
+++ b/sqpatch/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "sqpatch"
+version = "0.1.0"
+authors = ["Connie Hilarides "]
+edition = "2018"
+
+[dependencies]
+byteorder = "1.3.1"
diff --git a/sqpatch/src/aply.rs b/sqpatch/src/aply.rs
new file mode 100644
index 0000000..ed8fd23
--- /dev/null
+++ b/sqpatch/src/aply.rs
@@ -0,0 +1,25 @@
+use crate::{Parse, Stream};
+
+use std::io;
+
+use byteorder::{ReadBytesExt, BE};
+
+pub const SQPK_SIG: u32 = 0x53_51_50_4B; // SQPK
+
+#[derive(Copy, Clone, Debug)]
+pub struct Aply {
+ pub data: [u32; 4],
+}
+
+impl Parse for Aply {
+ fn parse(stream: &mut impl Stream) -> io::Result {
+ let data = [
+ stream.read_u32::()?,
+ stream.read_u32::()?,
+ stream.read_u32::()?,
+ stream.read_u32::()?,
+ ];
+
+ Ok(Aply { data })
+ }
+}
diff --git a/sqpatch/src/container.rs b/sqpatch/src/container.rs
new file mode 100644
index 0000000..d8c5069
--- /dev/null
+++ b/sqpatch/src/container.rs
@@ -0,0 +1,48 @@
+use crate::{Parse, Position, Stream};
+
+use byteorder::{ReadBytesExt, BE};
+
+#[derive(Copy, Clone)]
+pub struct Container {
+ /// Does not include the 4 bytes at the beginning of the data
+ pub length: u32,
+ pub signature: [u8; 4],
+ pub data: Position,
+}
+
+impl Container {
+ pub fn read_contents(&self, stream: &mut impl Stream) -> std::io::Result
+ where
+ D: Parse,
+ {
+ stream.seek_to(self.data)?;
+ D::parse(stream)
+ }
+}
+
+impl Parse for Container {
+ fn parse(stream: &mut impl Stream) -> std::io::Result {
+ let length = stream.read_u32::()?;
+ let mut signature = [0; 4];
+ stream.read_exact(&mut signature)?;
+ let data = stream.pos()?;
+ stream.seek_by(length as i64 + 4)?;
+
+ Ok(Container {
+ length,
+ signature,
+ data,
+ })
+ }
+}
+
+impl std::fmt::Debug for Container {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let sig = String::from_utf8_lossy(&self.signature);
+ fmt.debug_struct("Container")
+ .field("length", &self.length)
+ .field("signature", &sig)
+ .field("data", &self.data)
+ .finish()
+ }
+}
diff --git a/sqpatch/src/file_record.rs b/sqpatch/src/file_record.rs
new file mode 100644
index 0000000..04a4ee4
--- /dev/null
+++ b/sqpatch/src/file_record.rs
@@ -0,0 +1,94 @@
+use crate::{Parse, Position, Stream};
+
+use std::cmp::min;
+
+use byteorder::{ReadBytesExt, BE, LE};
+
+pub struct FileRecord {
+ pub header: [u8; 4],
+ pub unk0: u32,
+ pub file_offset: u32,
+ pub unk1: u32,
+ pub file_length: u32,
+ pub unk2: u32,
+ pub file_name: String,
+ pub chunks: Vec,
+ pub hash: [u8; 4],
+}
+
+impl Parse for FileRecord {
+ fn parse(stream: &mut impl Stream) -> std::io::Result {
+ let mut header = [0; 4];
+ stream.read_exact(&mut header)?;
+
+ let unk0 = stream.read_u32::()?;
+ let file_offset = stream.read_u32::()?;
+ let unk1 = stream.read_u32::()?;
+
+ let file_length = stream.read_u32::()?;
+ let file_name_len = stream.read_u32::()?;
+ let unk2 = stream.read_u32::()?;
+
+ let mut file_name = vec![0; file_name_len as usize];
+ stream.read_exact(&mut file_name)?;
+ file_name.pop();
+ let file_name = String::from_utf8_lossy(&file_name).into_owned();
+
+ let mut chunks = vec![];
+ loop {
+ if stream.peek_u32_le()? != 16 {
+ break;
+ }
+
+ chunks.push(FileChunk::parse(stream)?);
+ }
+
+ let mut hash = [0; 4];
+ stream.read_exact(&mut hash)?;
+
+ Ok(FileRecord {
+ header,
+ unk0,
+ file_offset,
+ unk1,
+ file_length,
+ unk2,
+ file_name,
+ chunks,
+ hash,
+ })
+ }
+}
+
+pub struct FileChunk {
+ pub unk0: u32,
+ pub unk1: u32,
+ pub data_len: u32,
+ pub expanded_len: u32,
+ pub data: Position,
+}
+
+impl Parse for FileChunk {
+ fn parse(stream: &mut impl Stream) -> std::io::Result {
+ let begin = stream.pos()?;
+
+ let unk0 = stream.read_u32::()?;
+ let unk1 = stream.read_u32::()?;
+
+ let data_len = stream.read_u32::()?;
+ let expanded_len = stream.read_u32::()?;
+
+ let data = stream.pos()?;
+
+ stream.seek_by(min(data_len, expanded_len) as i64)?;
+ stream.align_up_from(begin, 0x80)?;
+
+ Ok(FileChunk {
+ unk0,
+ unk1,
+ data_len,
+ expanded_len,
+ data,
+ })
+ }
+}
diff --git a/sqpatch/src/helpers.rs b/sqpatch/src/helpers.rs
new file mode 100644
index 0000000..9771901
--- /dev/null
+++ b/sqpatch/src/helpers.rs
@@ -0,0 +1,9 @@
+use std::fmt::UpperHex;
+
+pub struct HexFmt<'a, T: UpperHex>(pub &'a T);
+
+impl std::fmt::Debug for HexFmt<'_, T> {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ self.0.fmt(fmt)
+ }
+}
diff --git a/sqpatch/src/lib.rs b/sqpatch/src/lib.rs
new file mode 100644
index 0000000..97413cb
--- /dev/null
+++ b/sqpatch/src/lib.rs
@@ -0,0 +1,52 @@
+pub use crate::{
+ aply::Aply,
+ container::Container,
+ file_record::FileRecord,
+ sqpk::{PackType, Sqpk},
+ patch_file::PatchFile,
+};
+
+mod helpers;
+pub mod aply;
+pub mod container;
+pub mod file_record;
+pub mod sqpk;
+pub mod patch_file;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Position(pub u64);
+
+pub trait Stream: std::io::Read + std::io::Seek {
+ fn pos(&mut self) -> std::io::Result {
+ Ok(Position(self.seek(std::io::SeekFrom::Current(0))?))
+ }
+
+ fn seek_to(&mut self, pos: Position) -> std::io::Result {
+ self.seek(std::io::SeekFrom::Start(pos.0))
+ }
+
+ fn seek_by(&mut self, by: i64) -> std::io::Result {
+ self.seek(std::io::SeekFrom::Current(by))
+ }
+
+ fn align_up_from(&mut self, pos: Position, alignment: u64) -> std::io::Result {
+ let curr = self.pos()?;
+ let diff = curr.0.wrapping_sub(pos.0);
+ let new_pos = Position(curr.0 + alignment - (diff % alignment));
+ self.seek_to(new_pos)
+ }
+
+ fn peek_u32_le(&mut self) -> std::io::Result {
+ use byteorder::{ReadBytesExt,LE};
+ let cur = self.pos()?;
+ let val = self.read_u32::()?;
+ self.seek_to(cur)?;
+ Ok(val)
+ }
+}
+
+impl Stream for R where R: std::io::Read + std::io::Seek {}
+
+pub trait Parse: Sized {
+ fn parse(stream: &mut impl Stream) -> std::io::Result;
+}
diff --git a/sqpatch/src/patch_file.rs b/sqpatch/src/patch_file.rs
new file mode 100644
index 0000000..bd5db82
--- /dev/null
+++ b/sqpatch/src/patch_file.rs
@@ -0,0 +1,43 @@
+use crate::helpers::HexFmt;
+use crate::{Container, Parse, Stream};
+
+pub struct PatchFile {
+ pub signature: [u8; 8],
+ pub unknown0: u32,
+ pub containers: Vec,
+}
+
+impl Parse for PatchFile {
+ fn parse(stream: &mut impl Stream) -> std::io::Result {
+ use byteorder::{ReadBytesExt, BE};
+ let mut signature = [0; 8];
+ stream.read_exact(&mut signature)?;
+ let unknown0 = stream.read_u32::()?;
+
+ let mut containers = vec![];
+ loop {
+ let container = Container::parse(stream)?;
+ containers.push(container);
+ if &container.signature == b"EOF_" {
+ break;
+ }
+ }
+
+ Ok(PatchFile {
+ signature,
+ unknown0,
+ containers,
+ })
+ }
+}
+
+impl std::fmt::Debug for PatchFile {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let sig = String::from_utf8_lossy(&self.signature);
+ fmt.debug_struct("Container")
+ .field("signature", &sig)
+ .field("unknown0", &HexFmt(&self.unknown0))
+ .field("containers", &self.containers)
+ .finish()
+ }
+}
diff --git a/sqpatch/src/sqpk.rs b/sqpatch/src/sqpk.rs
new file mode 100644
index 0000000..eb8b815
--- /dev/null
+++ b/sqpatch/src/sqpk.rs
@@ -0,0 +1,55 @@
+use crate::{Parse, Position, Stream};
+
+use byteorder::{ReadBytesExt, BE};
+
+#[derive(Copy, Clone, Debug)]
+pub struct Sqpk {
+ pub length: u32,
+ pub data: Position,
+ pub kind: PackType,
+}
+
+impl Sqpk {
+ pub fn read_contents(&self, stream: &mut impl Stream) -> std::io::Result
+ where
+ D: Parse,
+ {
+ stream.seek_to(self.data)?;
+ D::parse(stream)
+ }
+}
+
+impl Parse for Sqpk {
+ fn parse(stream: &mut impl Stream) -> std::io::Result {
+ let length = stream.read_u32::()?;
+ let data = stream.pos()?;
+ let kind = PackType(stream.read_u16::()?);
+ stream.seek_by(length as i64 - 2)?;
+
+ Ok(Sqpk { length, data, kind })
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct PackType(pub u16);
+
+impl PackType {
+ pub const T: PackType = PackType(0x5400);
+ pub const X: PackType = PackType(0x5800);
+ pub const FA: PackType = PackType(0x4641);
+}
+
+impl std::fmt::Debug for PackType {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ if self.0 & 0xFF != 0 {
+ write!(
+ fmt,
+ "\"{}{}\"",
+ (self.0 >> 8) as u8 as char,
+ self.0 as u8 as char
+ )
+ } else {
+ write!(fmt, "\"{}\"", (self.0 >> 8) as u8 as char)
+ }
+ }
+}