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) + } + } +}