diff --git a/.gitignore b/.gitignore index 85698fc..9c36cfa 100644 --- a/.gitignore +++ b/.gitignore @@ -257,3 +257,7 @@ paket-files/ # Visual Studio Code .vscode + +CASCHost/*.db3 +CASCHost/listfile.csv +HttpProxy/logs/ diff --git a/CASCEdit/CASCEdit.csproj b/CASCEdit/CASCEdit.csproj index ca82919..8a719a6 100644 --- a/CASCEdit/CASCEdit.csproj +++ b/CASCEdit/CASCEdit.csproj @@ -6,10 +6,15 @@ AnyCPU;x64 - + true + + none + false + + diff --git a/CASCEdit/CASContainer.cs b/CASCEdit/CASContainer.cs index 6bf4714..f30eacb 100644 --- a/CASCEdit/CASContainer.cs +++ b/CASCEdit/CASContainer.cs @@ -53,16 +53,16 @@ public static void Open(CASSettings settings) if (BuildInfo["Version"] != Settings.Cache?.Version) { Settings.Cache?.Clean(); - LoadBuildInfo(Settings.SystemFilesPath); + LoadBuildInfo(Settings.BuildInfoPath); } else { Settings.SystemFilesPath = Settings.OutputPath; //Update system path to latest output build } } - else if (File.Exists(Path.Combine(settings.SystemFilesPath, ".build.info"))) + else if (File.Exists(Path.Combine(Settings.BuildInfoPath, ".build.info"))) { - LoadBuildInfo(Settings.SystemFilesPath); + LoadBuildInfo(Settings.BuildInfoPath); } else { @@ -99,7 +99,8 @@ private static void LoadBuildInfo(string path) return; } - BuildInfo = new SingleConfig(buildInfoPath, "Active", "1"); + Logger.LogInformation($"Loading build info for {Settings.Product}"); + BuildInfo = new SingleConfig(buildInfoPath, "Active", "1", Settings.Product); } private static void LoadBuildConfig() diff --git a/CASCEdit/CASSettings.cs b/CASCEdit/CASSettings.cs index 2e101aa..5bd3b42 100644 --- a/CASCEdit/CASSettings.cs +++ b/CASCEdit/CASSettings.cs @@ -15,6 +15,7 @@ public class CASSettings public bool Basic { get; set; } = false; public string BasePath { get; set; } public string SystemFilesPath { get; set; } + public string BuildInfoPath { get; set; } public string OutputPath { get; set; } public string PatchUrl { get; set; } public string Host { get; set; } @@ -23,6 +24,7 @@ public class CASSettings public LocaleFlags Locale { get; set; } = LocaleFlags.enUS; public bool StaticMode { get; set; } = false; + public string Product { get; set; } public HashSet DownloadLocations { get; set; } public HashSet CDNs { get; set; } @@ -38,6 +40,9 @@ public void Format() SystemFilesPath = BasePath; else if (!Directory.Exists(SystemFilesPath)) SystemFilesPath = Path.Combine(BasePath, SystemFilesPath); + + if (!Directory.Exists(BuildInfoPath)) + BuildInfoPath = Path.Combine(BasePath, BuildInfoPath); } } } diff --git a/CASCEdit/Configs/SingleConfig.cs b/CASCEdit/Configs/SingleConfig.cs index f1967e0..b300355 100644 --- a/CASCEdit/Configs/SingleConfig.cs +++ b/CASCEdit/Configs/SingleConfig.cs @@ -3,8 +3,6 @@ using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; -using CASCEdit.Handlers; using CASCEdit.IO; namespace CASCEdit.Configs @@ -15,6 +13,7 @@ public class SingleConfig private List Lines; private string BaseFile; private string NewLineChar; + private string productCode; public string this[string key] { @@ -26,11 +25,14 @@ public string this[string key] } } - public SingleConfig(string file, string key, string value) + public SingleConfig(string file, string key, string value, string product = null) { BaseFile = file; + productCode = product; + + + Stream stream; - Stream stream; if (Uri.IsWellFormedUriString(file, UriKind.Absolute)) // URLs require streaming { @@ -123,6 +125,18 @@ private void Parse(BinaryReader reader, string key, string value) } else { + + if (Path.GetFileNameWithoutExtension(BaseFile) == ".build") + { + // ignore non-relevant product entry in .build.info + int productidx = fields.IndexOf("Product"); + if (productidx >= 0 && tokens[productidx] != productCode) + { + Lines.RemoveAt(i--); + continue; + } + } + // ignore lines not matching the indentifier key value int keyidx = fields.IndexOf(key); if (keyidx >= 0 && tokens[keyidx] != value) diff --git a/CASCEdit/Handlers/RootHandler.cs b/CASCEdit/Handlers/RootHandler.cs index 85c85f4..5c5b1ce 100644 --- a/CASCEdit/Handlers/RootHandler.cs +++ b/CASCEdit/Handlers/RootHandler.cs @@ -37,7 +37,7 @@ public class RootHandler : IDisposable public RootHandler() { - GlobalRoot = new RootChunk() { ContentFlags = ContentFlags.None, LocaleFlags = LocaleFlags.All_WoW }; + GlobalRoot = new RootChunk() { ContentFlags = ContentFlags.None, LocaleFlags = LocaleFlags.All_WoW, Count = 0 }; encodingMap = new EncodingMap(EncodingType.ZLib, 9); } @@ -47,7 +47,7 @@ public RootHandler(Stream data, LocaleFlags locale, uint minimumid = 0, bool onl this.locale = locale; string cdnPath = Helper.GetCDNPath("listfile.csv"); - if (!(File.Exists(Path.Combine(CASContainer.Settings.OutputPath, cdnPath))) && onlineListfile) + if (!(File.Exists(cdnPath)) && onlineListfile) { CASContainer.Logger.LogInformation("Downloading listfile from WoW.Tools"); ListFileClient.DownloadFile("https://wow.tools/casc/listfile/download/csv/unverified", cdnPath); @@ -65,7 +65,7 @@ public RootHandler(Stream data, LocaleFlags locale, uint minimumid = 0, bool onl } else { - stream.BaseStream.Position = 0; + stream.BaseStream.Position -= 4; } long length = stream.BaseStream.Length; @@ -80,9 +80,25 @@ public RootHandler(Stream data, LocaleFlags locale, uint minimumid = 0, bool onl parsedFiles += (int)chunk.Count; - // set the global root - if (chunk.LocaleFlags == LocaleFlags.All_WoW && chunk.ContentFlags == ContentFlags.None) - GlobalRoot = chunk; + //Console.WriteLine("Chunk: {0} {1} (size {2})", chunk.ContentFlags, chunk.LocaleFlags, chunk.Count); + + // set the global root if not already set + if (GlobalRoot == null) + { + if (chunk.LocaleFlags == LocaleFlags.All_WoW && chunk.ContentFlags == ContentFlags.F00080000) + { + GlobalRoot = chunk; + } + else if (chunk.LocaleFlags == LocaleFlags.All_WoW_Classic && chunk.ContentFlags == ContentFlags.None) + { + GlobalRoot = chunk; + } + + if (GlobalRoot != null) + { + CASContainer.Logger.LogInformation($"Global root found. Flags: {chunk.ContentFlags}, {chunk.LocaleFlags}. Count: {chunk.Count}"); + } + } uint fileDataIndex = 0; for (int i = 0; i < chunk.Count; i++) diff --git a/CASCEdit/Structs/RootStructs.cs b/CASCEdit/Structs/RootStructs.cs index 7ee83b3..c78988b 100644 --- a/CASCEdit/Structs/RootStructs.cs +++ b/CASCEdit/Structs/RootStructs.cs @@ -28,23 +28,36 @@ public enum LocaleFlags : uint ptBR = 0x4000, itIT = 0x8000, ptPT = 0x10000, - All_WoW = enUS | koKR | frFR | deDE | zhCN | esES | zhTW | enGB | esMX | ruRU | ptBR | itIT | ptPT + All_WoW = enUS | koKR | frFR | deDE | zhCN | esES | zhTW | enGB | esMX | ruRU | ptBR | itIT | ptPT, + All_WoW_Classic = enUS | koKR | frFR | deDE | zhCN | esES | zhTW | enGB | esMX | ruRU | ptBR | ptPT } [Flags] public enum ContentFlags : uint { None = 0, - F00000001 = 0x1, + F00000001 = 0x1, // seen on *.wlm files F00000002 = 0x2, F00000004 = 0x4, - F00000008 = 0x8, // added in 7.2.0.23436 - F00000010 = 0x10, // added in 7.2.0.23436 - LowViolence = 0x80, // many models have this flag - NoNames = 0x10000000, - F20000000 = 0x20000000, // added in 21737 - Bundle = 0x40000000, - NoCompression = 0x80000000 // sounds have this flag + Windows = 0x8, // added in 7.2.0.23436 + MacOS = 0x10, // added in 7.2.0.23436 + Alternate = 0x80, // many chinese models have this flag + F00000100 = 0x100, // apparently client doesn't load files with this flag + F00000800 = 0x800, // only seen on UpdatePlugin files + F00020000 = 0x20000, // new 9.0 + F00040000 = 0x40000, // new 9.0 + F00080000 = 0x80000, // new 9.0 + F00100000 = 0x100000, // new 9.0 + F00200000 = 0x200000, // new 9.0 + F00400000 = 0x400000, // new 9.0 + F00800000 = 0x800000, // new 9.0 + F02000000 = 0x2000000, // new 9.0 + F04000000 = 0x4000000, // new 9.0 + Encrypted = 0x8000000, // encrypted may be? + NoNameHash = 0x10000000, // doesn't have name hash? + F20000000 = 0x20000000, // added in 21737, used for many cinematics + Bundle = 0x40000000, // not related to wow, used as some old overwatch hack + NotCompressed = 0x80000000 // sounds have this flag } public class RootChunk diff --git a/CASCHost/AppSettings.cs b/CASCHost/AppSettings.cs index bfca81c..733e1ee 100644 --- a/CASCHost/AppSettings.cs +++ b/CASCHost/AppSettings.cs @@ -13,20 +13,26 @@ namespace CASCHost public class AppSettings { public uint MinimumFileDataId { get; set; } // the minimum file id for new files - public bool OnlineListFile { get; set; } = false; // fetchs from WoW.Tools the lastest listfile + public bool OnlineListFile { get; set; } = true; // fetchs from WoW.Tools the lastest listfile public bool BNetAppSupport { get; set; } = false; // create install and download files? public bool StaticMode { get; set; } = false; // Build CDN file struct public string RebuildPassword { get; set; } = ""; - public string HostDomain { get; set; } // accessible address of this server + public string GameDirectory { get; set; } = ""; + + public string Product { get; set; } = "wow"; + public string SqliteDatabase { get; set; } = "caschost.db3"; + + public string HostDomain { get; set; } // accessible address of this server public string[] CDNs { get; set; } // custom CDNs i.e. local client CASC archive clone - public string SqlConnection { get; set; } // database connection string public string PatchUrl { get; set; } // offical blizzard patch url i.e. http://us.patch.battle.net:1119 public string Locale { get; set; } // preferred locale for content public string[] DirectoryHash { get; set; } // hashes of directories for offline change detection - - public void Save(IHostingEnvironment env) + + public string GameVersion { get; set; } = ""; // Game version the cdn was built for + + public void Save(IHostingEnvironment env) { if (CDNs == null) CDNs = new string[0]; @@ -35,7 +41,7 @@ public void Save(IHostingEnvironment env) var obj = new ExpandoObject() as IDictionary; obj.Add(GetType().Name, this); - using (FileStream fs = new FileStream(Path.Combine(env.ContentRootPath, "appsettings.json"), FileMode.Create, FileAccess.Write, FileShare.Read)) + using (FileStream fs = new FileStream(Path.Combine(env.ContentRootPath, $"appsettings.{Product}.json"), FileMode.Create, FileAccess.Write, FileShare.Read)) using (StreamWriter sw = new StreamWriter(fs)) { sw.Write(JsonConvert.SerializeObject(obj, Formatting.Indented)); diff --git a/CASCHost/CASCHost.csproj b/CASCHost/CASCHost.csproj index a8d6731..1368d0d 100644 --- a/CASCHost/CASCHost.csproj +++ b/CASCHost/CASCHost.csproj @@ -8,24 +8,43 @@ + + none + false + + + + + + + + + - + + - + PreserveNewest - + + Always + + + PreserveNewest + + Always @@ -35,7 +54,10 @@ - + + Always + + Always diff --git a/CASCHost/Cache.cs b/CASCHost/Cache.cs index cb4b2fd..c95f2d6 100644 --- a/CASCHost/Cache.cs +++ b/CASCHost/Cache.cs @@ -3,7 +3,6 @@ using CASCEdit.Helpers; using CASCEdit.Structs; using Microsoft.AspNetCore.Hosting; -using MySql.Data.MySqlClient; using System; using System.Collections.Generic; using System.Data; @@ -13,6 +12,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Data.SQLite; namespace CASCHost { @@ -31,17 +31,41 @@ public class Cache : ICache private Dictionary RootFiles; private Queue queries = new Queue(); private bool firstrun = true; + private string dbFileName => Startup.Settings.SqliteDatabase; - public Cache(IHostingEnvironment environment) + public Cache(IHostingEnvironment environment) { env = environment; Startup.Logger.LogInformation("Loading cache..."); Load(); } - - public void AddOrUpdate(CacheEntry item) + public void wipeDB() + { + if (!File.Exists(dbFileName)) + return; + + using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + dbFileName + ";Version=3;")) + using (SQLiteCommand command = new SQLiteCommand()) + { + try + { + connection.Open(); + command.Connection = connection; + command.CommandText = WIPE_RECORDS; + command.ExecuteNonQuery(); + } + catch (SQLiteException ex) + { + Startup.Logger?.LogFile(ex.Message); + Startup.Logger?.LogAndThrow(CASCEdit.Logging.LogType.Critical, "Something wrong with the database."); + } + } + } + + + public void AddOrUpdate(CacheEntry item) { if(firstrun) { @@ -61,7 +85,7 @@ public void AddOrUpdate(CacheEntry item) RootFiles[item.Path] = item; - queries.Enqueue(string.Format(REPLACE_RECORD, MySqlHelper.EscapeString(item.Path), item.FileDataId, item.NameHash, item.CEKey, item.EKey)); + queries.Enqueue(string.Format(REPLACE_RECORD, item.Path, item.FileDataId, item.NameHash, item.CEKey, item.EKey)); return; } @@ -71,7 +95,7 @@ public void AddOrUpdate(CacheEntry item) var existing = RootFiles.Where(x => x.Value.FileDataId == item.FileDataId).ToArray(); foreach (var ex in existing) { - queries.Enqueue(string.Format(DELETE_RECORD, MySqlHelper.EscapeString(item.Path))); + queries.Enqueue(string.Format(DELETE_RECORD, item.Path)); RootFiles.Remove(ex.Key); } } @@ -79,14 +103,14 @@ public void AddOrUpdate(CacheEntry item) // Add RootFiles.Add(item.Path, item); - queries.Enqueue(string.Format(REPLACE_RECORD, MySqlHelper.EscapeString(item.Path), item.FileDataId, item.NameHash, item.CEKey, item.EKey)); + queries.Enqueue(string.Format(REPLACE_RECORD, item.Path, item.FileDataId, item.NameHash, item.CEKey, item.EKey)); } public void Remove(string file) { if (RootFiles.ContainsKey(file)) { - queries.Enqueue(string.Format(DELETE_RECORD, MySqlHelper.EscapeString(RootFiles[file].Path))); + queries.Enqueue(string.Format(DELETE_RECORD, RootFiles[file].Path)); RootFiles.Remove(file); } } @@ -118,33 +142,37 @@ public void Clean() #region SQL Methods private void LoadOrCreate() { - Version = new SingleConfig(Path.Combine(env.WebRootPath, "SystemFiles", ".build.info"), "Active", "1")["Version"]; - using (MySqlConnection connection = new MySqlConnection(Startup.Settings.SqlConnection)) - using (MySqlCommand command = new MySqlCommand()) - { - try - { - connection.Open(); - command.Connection = connection; - - // create data table - command.CommandText = CREATE_DATA_TABLE; - command.ExecuteNonQuery(); - - // load data - command.CommandText = LOAD_DATA; - ReadAll(command.ExecuteReader()); - - // purge old data - command.CommandText = PURGE_RECORDS; - command.ExecuteNonQuery(); - } - catch(MySqlException ex) - { - Startup.Logger?.LogFile(ex.Message); - Startup.Logger?.LogAndThrow(CASCEdit.Logging.LogType.Critical, "Unable to connect to the database."); - } - } + Version = new SingleConfig(Path.Combine(env.WebRootPath, "SystemFiles", ".build.info"), "Active", "1", Startup.Settings.Product)["Version"]; + + if (!File.Exists(dbFileName)) + SQLiteConnection.CreateFile(dbFileName); + + using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + dbFileName + ";Version=3;")) + using (SQLiteCommand command = new SQLiteCommand()) + { + try + { + connection.Open(); + command.Connection = connection; + + // create data table + command.CommandText = CREATE_DATA_TABLE; + command.ExecuteNonQuery(); + + // load data + command.CommandText = LOAD_DATA; + ReadAll(command.ExecuteReader()); + + // purge old data + command.CommandText = PURGE_RECORDS; + command.ExecuteNonQuery(); + } + catch (SQLiteException ex) + { + Startup.Logger?.LogFile(ex.Message); + Startup.Logger?.LogAndThrow(CASCEdit.Logging.LogType.Critical, "Something wrong with the database."); + } + } } private void ReadAll(DbDataReader reader) @@ -158,35 +186,35 @@ private void ReadAll(DbDataReader reader) CacheEntry entry = new CacheEntry() { Path = reader.GetFieldValue(1), - FileDataId = reader.GetFieldValue(2), - NameHash = reader.GetFieldValue(3), + FileDataId = Convert.ToUInt32(reader.GetFieldValue(2)), + NameHash = Convert.ToUInt64(reader.GetFieldValue(3)), CEKey = new MD5Hash(reader.GetFieldValue(4).ToByteArray()), EKey = new MD5Hash(reader.GetFieldValue(5).ToByteArray()) }; // keep files that still exist or are special and not flagged to be deleted - bool keep = File.Exists(Path.Combine(env.WebRootPath, "Data", entry.Path)) && reader.IsDBNull(6); + bool keep = File.Exists(Path.Combine(env.WebRootPath, "Data", Startup.Settings.Product, entry.Path)) && reader.IsDBNull(6); if (keep || entry.FileDataId == 0) { RootFiles.Add(entry.Path, entry); } else if (reader.IsDBNull(6)) // needs to be marked for purge { - queries.Enqueue(string.Format(DELETE_RECORD, MySqlHelper.EscapeString(entry.Path))); + queries.Enqueue(string.Format(DELETE_RECORD, entry.Path)); Startup.Logger.LogInformation($"{entry.Path} missing. Marked for removal."); ToPurge.Add(entry.Path); } - else if (reader.GetFieldValue(6) <= DateTime.Now.Date) // needs to be purged + else if (Convert.ToDateTime(reader.GetFieldValue(6)) <= DateTime.Now.Date) // needs to be purged { ToPurge.Add(entry.Path); string cdnpath = Helper.GetCDNPath(entry.EKey.ToString(), "", "", Startup.Settings.StaticMode); - string filepath = Path.Combine(env.WebRootPath, "Output", cdnpath); + string filepath = Path.Combine(env.WebRootPath, "Output", Startup.Settings.Product, cdnpath); if (File.Exists(filepath)) File.Delete(filepath); - if (File.Exists(Path.Combine(env.WebRootPath, "Data", entry.Path))) - File.Delete(Path.Combine(env.WebRootPath, "Data", entry.Path)); + if (File.Exists(Path.Combine(env.WebRootPath, "Data", Startup.Settings.Product, entry.Path))) + File.Delete(Path.Combine(env.WebRootPath, "Data", Startup.Settings.Product, entry.Path)); } } @@ -212,52 +240,52 @@ private void BatchTransaction() for (int i = 0; i < count; i++) sb.AppendLine(queries.Dequeue()); - try - { - using (MySqlConnection connection = new MySqlConnection(Startup.Settings.SqlConnection)) - using (MySqlCommand command = new MySqlCommand(sb.ToString(), connection)) - { - connection.Open(); - command.ExecuteNonQuery(); - } - } - catch (MySqlException ex) - { - Startup.Logger.LogError("SQLERR: " + ex.Message); - } + Startup.Logger.LogInformation("Updating..."); + + using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + dbFileName + ";Version=3;")) + using (SQLiteCommand command = new SQLiteCommand(sb.ToString(), connection)) + { + try + { + connection.Open(); + command.ExecuteNonQuery(); + } + catch (SQLiteException ex) + { + Startup.Logger?.LogFile(ex.Message); + Startup.Logger?.LogAndThrow(CASCEdit.Logging.LogType.Critical, "DB Error."); + } + } } } - #endregion - - #region SQL Strings - - private const string CREATE_DATA_TABLE = "SET GLOBAL innodb_file_format=Barracuda; " + - "SET GLOBAL innodb_file_per_table=ON; " + - "SET GLOBAL innodb_large_prefix=ON; " + - " " + - "CREATE TABLE IF NOT EXISTS `root_entries` ( " + - " `Id` BIGINT NOT NULL AUTO_INCREMENT, " + - " `Path` VARCHAR(1024), " + - " `FileDataId` INT UNSIGNED, " + - " `Hash` BIGINT UNSIGNED, " + - " `MD5` VARCHAR(32), " + - " `BLTE` VARCHAR(32), " + - " `PurgeAt` DATE NULL, " + - " PRIMARY KEY(`Id`), " + - " UNIQUE INDEX `Path` (`Path`) " + - ") COLLATE = 'utf8_general_ci' ENGINE=InnoDB ROW_FORMAT=DYNAMIC; "; + #endregion + + #region SQL Strings + + private const string CREATE_DATA_TABLE = "CREATE TABLE IF NOT EXISTS `root_entries` (" + + "`Id` INTEGER PRIMARY KEY, " + + "`Path` TEXT UNIQUE," + + " `FileDataId` INTEGER," + + "`Hash` TEXT," + + "`MD5` TEXT," + + "`BLTE` TEXT," + + "`PurgeAt` TEXT NULL" + + ");"; private const string LOAD_DATA = "SELECT * FROM `root_entries`;"; - private const string REPLACE_RECORD = "INSERT INTO `root_entries` (`Path`, `FileDataId`, `Hash`, `MD5`, `BLTE`, `PurgeAt`) VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', NULL) " + - "ON DUPLICATE KEY UPDATE `FileDataId` = VALUES(`FileDataId`), `Hash` = VALUES(`Hash`), `MD5` = VALUES(`MD5`), `BLTE` = VALUES(`BLTE`), `PurgeAt` = VALUES(`PurgeAt`);"; + private const string REPLACE_RECORD = "INSERT INTO `root_entries` (`Path`, `FileDataId`, `Hash`, `MD5`, `BLTE`, `PurgeAt`) VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', NULL) " + + "ON CONFLICT(`Path`) DO UPDATE SET `FileDataId` = excluded.FileDataId, `Hash` = excluded.hash, `MD5` = excluded.md5, `BLTE` = excluded.blte, `PurgeAt` = excluded.PurgeAt;"; + + + private const string DELETE_RECORD = "UPDATE `root_entries` SET `PurgeAt` = datetime('now') WHERE `Path` = '{0}';"; - private const string DELETE_RECORD = "UPDATE `root_entries` SET `PurgeAt` = DATE_ADD(CAST(NOW() AS DATE), INTERVAL 1 WEEK) WHERE `Path` = '{0}'; "; + private const string PURGE_RECORDS = "DELETE FROM `root_entries` WHERE `PurgeAt` < datetime('now');"; - private const string PURGE_RECORDS = "DELETE FROM `root_entries` WHERE `PurgeAt` < CAST(NOW() AS DATE); "; + private const string WIPE_RECORDS = "DELETE FROM `root_entries`"; - #endregion + #endregion - } + } } diff --git a/CASCHost/DataWatcher.cs b/CASCHost/DataWatcher.cs index 961b9a3..9b33da6 100644 --- a/CASCHost/DataWatcher.cs +++ b/CASCHost/DataWatcher.cs @@ -1,4 +1,5 @@ using CASCEdit; +using CASCEdit.Configs; using CASCEdit.Structs; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; @@ -22,23 +23,24 @@ public class DataWatcher : IDisposable private IHostingEnvironment _env; private FileSystemWatcher watcher; + private FileSystemWatcher gameDirectoryWatcher; private Timer timer; private readonly string dataPath; private readonly string outputPath; + private readonly string buildInfoPath; private ConcurrentDictionary changes; private CASSettings settings; - private DateTime lastBuild; - public DataWatcher(IHostingEnvironment env) { _env = env; - dataPath = Path.Combine(env.WebRootPath, "Data"); - outputPath = Path.Combine(env.WebRootPath, "Output"); + dataPath = Path.Combine(env.WebRootPath, "Data", Startup.Settings.Product); + outputPath = Path.Combine(env.WebRootPath, "Output", Startup.Settings.Product); + buildInfoPath = Path.Combine(env.WebRootPath, "SystemFiles"); changes = new ConcurrentDictionary(); - lastBuild = new DateTime(); - LoadSettings(); + + LoadSettings(); if (settings.StaticMode) { ForceRebuild(); @@ -46,14 +48,21 @@ public DataWatcher(IHostingEnvironment env) } //Rebuild if files have changed since last run otherwise wait for a change to occur - if (!IsRebuildRequired()) - timer = new Timer(UpdateCASCDirectory, null, Timeout.Infinite, Timeout.Infinite); + if (IsRebuildRequired()) + { + ForceRebuild(); + } + else + { + timer = new Timer(UpdateCASCDirectory, null, Timeout.Infinite, Timeout.Infinite); + } + watcher = new FileSystemWatcher() { Path = dataPath, NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime, - EnableRaisingEvents = false /*Startup.Settings.RebuildOnChange*/, + EnableRaisingEvents = false, IncludeSubdirectories = true }; @@ -61,28 +70,58 @@ public DataWatcher(IHostingEnvironment env) watcher.Created += LogChange; watcher.Deleted += LogChange; watcher.Renamed += LogChange; + + gameDirectoryWatcher = new FileSystemWatcher() + { + Path = Startup.Settings.GameDirectory, + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime, + EnableRaisingEvents = true, + IncludeSubdirectories = false + }; + + gameDirectoryWatcher.Changed += onGameDirectoryChange; + gameDirectoryWatcher.Created += onGameDirectoryChange; } + #region Change Detection - #region Change Detection - private void LogChange(object sender, FileSystemEventArgs e) - { - //Ignore folder changes - rename is handled below all files fire this event themselves - if (IsDirectory(e.FullPath)) - return; + private void onGameDirectoryChange(object sender, FileSystemEventArgs e) + { + //Ignore folder changes + if (IsDirectory(e.FullPath)) + return; - //Assume anything extensionless is a folder - if (string.IsNullOrWhiteSpace(Path.GetExtension(e.FullPath))) - return; + + if (e.Name == ".build.info" && File.Exists(e.FullPath)) + { + Startup.Logger.LogInformation($".build.info changes detected. Updating..."); + Startup.ImportBuildInfo(Path.Combine(buildInfoPath, ".build.info")); + + if (!RebuildInProgress && IsRebuildRequired()) + ForceRebuild(); + + return; + } + } + + private void LogChange(object sender, FileSystemEventArgs e) + { + //Ignore folder changes - rename is handled below all files fire this event themselves + if (IsDirectory(e.FullPath)) + return; + + //Assume anything extensionless is a folder + if (string.IsNullOrWhiteSpace(Path.GetExtension(e.FullPath))) + return; //Update or add change changes.AddOrUpdate(e.FullPath, e, (k, v) => e); //Add delay for user to finishing changing files timer.Change(30 * 1000, 0); - } + } - private void LogChange(object sender, RenamedEventArgs e) + private void LogChange(object sender, RenamedEventArgs e) { if (IsDirectory(e.FullPath)) { @@ -113,21 +152,33 @@ private void LogChange(object sender, RenamedEventArgs e) #region CASC Update public void ForceRebuild() { + //Wipe DB + Startup.Cache.wipeDB(); + + //Wipe CASC + WipeCASCDirectory(); + + changes.Clear(); + //Rebuild all files var files = Directory.EnumerateFiles(dataPath, "*.*", SearchOption.AllDirectories).OrderBy(f => f); foreach (var f in files) { - if(File.GetLastWriteTime(f) >= lastBuild || File.GetCreationTime(f) >= lastBuild) - { - var args = new FileSystemEventArgs(WatcherChangeTypes.Changed, Path.GetDirectoryName(f), Path.GetFileName(f)); - changes.AddOrUpdate(f, args, (k, v) => args); - } + var args = new FileSystemEventArgs(WatcherChangeTypes.Changed, Path.GetDirectoryName(f), Path.GetFileName(f)); + changes.AddOrUpdate(f, args, (k, v) => args); } timer = new Timer(UpdateCASCDirectory, null, 0, Timeout.Infinite); } - private void UpdateCASCDirectory(object obj) + private void WipeCASCDirectory() + { + Directory.Delete(outputPath, true); + Directory.CreateDirectory(outputPath); + } + + + private void UpdateCASCDirectory(object obj) { if (RebuildInProgress) //Saving already wait for build to finish { @@ -168,6 +219,7 @@ private void UpdateCASCDirectory(object obj) string cascpath = GetCASCPath(fullpath); string oldpath = GetCASCPath((change as RenamedEventArgs)?.OldFullPath + ""); + switch (change.ChangeType) { case WatcherChangeTypes.Renamed: @@ -195,7 +247,12 @@ private void UpdateCASCDirectory(object obj) GetDirectoryHash(dataPath), GetDirectoryHash(outputPath) }; - Startup.Settings.Save(_env); + + string GameVersion = new SingleConfig(Path.Combine(buildInfoPath, ".build.info"), "Active", "1", Startup.Settings.Product)["Version"]; + Startup.Settings.GameVersion = GameVersion; + + + Startup.Settings.Save(_env); sw.Stop(); Startup.Logger.LogWarning($"CASC rebuild finished [{DateTime.Now}] - {Math.Round(sw.Elapsed.TotalSeconds, 3)}s"); @@ -205,7 +262,6 @@ private void UpdateCASCDirectory(object obj) Environment.Exit(0); } - lastBuild = DateTime.Now; RebuildInProgress = false; } @@ -213,7 +269,7 @@ private string GetCASCPath(string file) { string lookup = new DirectoryInfo(_env.WebRootPath).Name; string[] parts = file.Split(Path.DirectorySeparatorChar); - return Path.Combine(parts.Skip(Array.IndexOf(parts, lookup) + 2).ToArray()); //Remove top directories + return Path.Combine(parts.Skip(Array.IndexOf(parts, lookup) + 3).ToArray()); //Remove top directories } #endregion @@ -222,8 +278,19 @@ private bool IsRebuildRequired() { Startup.Logger.LogInformation("Offline file change check."); - //No data files - if (!Directory.EnumerateFiles(dataPath, "*.*", SearchOption.AllDirectories).Any()) + string BuildInfoVersion = new SingleConfig(Path.Combine(buildInfoPath, ".build.info"), "Active", "1", Startup.Settings.Product)["Version"]; + if (BuildInfoVersion != Startup.Settings.GameVersion) + { + Startup.Logger.LogInformation($"New version detected: {BuildInfoVersion}. Rebuilding..."); + return true; + } + else + { + Startup.Logger.LogInformation($"Same version, nothing to do here."); + } + + //No data files + if (!Directory.EnumerateFiles(dataPath, "*.*", SearchOption.AllDirectories).Any()) return false; string[] hashes = new[] @@ -235,7 +302,6 @@ private bool IsRebuildRequired() //Check for offline changes if (!hashes.SequenceEqual(Startup.Settings.DirectoryHash) /*|| Startup.Settings.RebuildOnLoad*/) { - ForceRebuild(); return true; } @@ -269,14 +335,16 @@ private void LoadSettings() { Host = Startup.Settings.HostDomain, BasePath = _env.WebRootPath, - OutputPath = "Output", - SystemFilesPath = "SystemFiles", + OutputPath = Path.Combine("Output", Startup.Settings.Product), + SystemFilesPath = Path.Combine("SystemFiles", Startup.Settings.Product), + BuildInfoPath = this.buildInfoPath, PatchUrl = Startup.Settings.PatchUrl, Logger = Startup.Logger, Cache = Startup.Cache, Locale = locale, CDNs = new HashSet(), - StaticMode = Startup.Settings.StaticMode + StaticMode = Startup.Settings.StaticMode, + Product = Startup.Settings.Product }; @@ -303,6 +371,9 @@ public void Dispose() timer = null; watcher?.Dispose(); watcher = null; - } + gameDirectoryWatcher?.Dispose(); + gameDirectoryWatcher = null; + + } } } diff --git a/CASCHost/FileProvider.cs b/CASCHost/FileProvider.cs index c9f31cc..c60f304 100644 --- a/CASCHost/FileProvider.cs +++ b/CASCHost/FileProvider.cs @@ -21,7 +21,7 @@ public class FileProvider : IFileProvider public FileProvider(IHostingEnvironment env, IHttpContextAccessor contextAccessor) { this.contextAccessor = contextAccessor; - this.outPath = Path.Combine(env.WebRootPath, "Output"); + this.outPath = Path.Combine(env.WebRootPath, "Output", Startup.Settings.Product); } diff --git a/CASCHost/Program.cs b/CASCHost/Program.cs index 6534b21..ac6b408 100644 --- a/CASCHost/Program.cs +++ b/CASCHost/Program.cs @@ -11,14 +11,17 @@ namespace CASCHost { public class Program { - public static void Main(string[] args) + public static string Product; + + public static void Main(string[] args) { + Product = (args.Count() == 0) ? "wow" : args[0]; Directory.CreateDirectory("wwwroot"); - var config = new ConfigurationBuilder() + var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("hosting.json", optional: true) + .AddJsonFile($"hosting.{Product}.json", optional: true) .Build(); var host = new WebHostBuilder() diff --git a/CASCHost/Properties/launchSettings.json b/CASCHost/Properties/launchSettings.json index f32e64c..fb2918c 100644 --- a/CASCHost/Properties/launchSettings.json +++ b/CASCHost/Properties/launchSettings.json @@ -16,7 +16,7 @@ }, "CASCHost": { "commandName": "Project", - "workingDirectory": "", + "commandLineArgs": "wow", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/CASCHost/Start Classic.bat b/CASCHost/Start Classic.bat new file mode 100644 index 0000000..3014c96 --- /dev/null +++ b/CASCHost/Start Classic.bat @@ -0,0 +1,2 @@ +CASCHost.exe wow_classic +pause \ No newline at end of file diff --git a/CASCHost/Start Retail.bat b/CASCHost/Start Retail.bat new file mode 100644 index 0000000..aecfc11 --- /dev/null +++ b/CASCHost/Start Retail.bat @@ -0,0 +1,2 @@ +CASCHost.exe wow +pause \ No newline at end of file diff --git a/CASCHost/Start.bat b/CASCHost/Start.bat deleted file mode 100644 index 8dacfd7..0000000 --- a/CASCHost/Start.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet CASCHost.dll \ No newline at end of file diff --git a/CASCHost/Startup.cs b/CASCHost/Startup.cs index 5c920b1..1eeb168 100644 --- a/CASCHost/Startup.cs +++ b/CASCHost/Startup.cs @@ -11,6 +11,11 @@ using Microsoft.AspNetCore.Http; using System.Text.RegularExpressions; using Microsoft.Extensions.FileProviders; +using Microsoft.Win32; +using System.Security.Cryptography; +using System.Text; +using CASCEdit; +using CASCEdit.Configs; namespace CASCHost { @@ -28,7 +33,7 @@ public Startup(ILoggerFactory loggerFactory) public void ConfigureServices(IServiceCollection services) { - var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); + var builder = new ConfigurationBuilder().AddJsonFile($"appsettings.{Program.Product}.json", optional: false, reloadOnChange: true); IConfigurationRoot configuration = builder.Build(); services.AddOptions(); @@ -42,6 +47,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IService if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); + //Load settings + Settings = settings.Value; + //Set file handler app.UseStaticFiles(new StaticFileOptions() { @@ -50,16 +58,25 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IService ServeUnknownFileTypes = true }); - //Load settings - Settings = settings.Value; + //Create directories + Directory.CreateDirectory(Path.Combine(env.WebRootPath, "Data")); + Directory.CreateDirectory(Path.Combine(env.WebRootPath, "Data", Settings.Product)); - //Create directories - Directory.CreateDirectory(Path.Combine(env.WebRootPath, "Data")); Directory.CreateDirectory(Path.Combine(env.WebRootPath, "Output")); - Directory.CreateDirectory(Path.Combine(env.WebRootPath, "SystemFiles")); + Directory.CreateDirectory(Path.Combine(env.WebRootPath, "Output", Settings.Product)); + + Directory.CreateDirectory(Path.Combine(env.WebRootPath, "SystemFiles")); + Directory.CreateDirectory(Path.Combine(env.WebRootPath, "SystemFiles", Settings.Product)); + + if (!Directory.Exists(Settings.GameDirectory)) + { + GuessGameDirectory(env); + } + + Logger.LogInformation($"Using product: {Settings.Product}"); - //Check installation is corect - StartUpChecks(env); + //Check installation is corect + StartUpChecks(env); //Load cache Cache = new Cache(env); @@ -68,11 +85,57 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IService Watcher = new DataWatcher(env); } + public static void ImportBuildInfo(string targetPath) + { + string remoteBuildInfoPath = Path.Combine(Settings.GameDirectory, ".build.info"); + if (File.Exists(remoteBuildInfoPath)) + { + Logger.LogInformation("Importing .build.info from wow folder..."); + + try + { + File.Delete(targetPath); + File.Move(remoteBuildInfoPath, targetPath); + } catch (IOException e) { + Logger.LogError(e.Message); + Logger.LogError("Failed to import .build.info. You might need to do it manually."); + + return; + } + + return; + } + + Logger.LogInformation(".build.info not found in wow folder, skipping..."); + } + + private static void GuessGameDirectory(IHostingEnvironment env) + { + string wowRoot = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Blizzard Entertainment\World of Warcraft\", "installPath", ""); + + while (Directory.Exists(wowRoot)) + { + if (File.Exists(Path.Combine(wowRoot, ".product.db"))) + { + Settings.GameDirectory = wowRoot.TrimEnd('/'); + Settings.Save(env); + + Logger.LogInformation($"GameDirectory set to {Settings.GameDirectory}"); + + return; + } + else + { + wowRoot = Path.GetDirectoryName(wowRoot); + } + } + + Logger.LogWarning($"Failed to set GameDirectory via registry."); + } private void StartUpChecks(IHostingEnvironment env) { - bool exit = false; const string DOMAIN_REGEX = @"^(?:.*?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^\/\n]+)"; //Normalise values @@ -80,20 +143,33 @@ private void StartUpChecks(IHostingEnvironment env) Settings.HostDomain = Settings.HostDomain.TrimEnd('/'); Settings.Save(env); - //.build.info check - if (!File.Exists(Path.Combine(env.WebRootPath, "SystemFiles", ".build.info"))) + //Game Directory check + if (!File.Exists(Path.Combine(Settings.GameDirectory, "World of Warcraft Launcher.exe"))) + { + Logger.LogCritical($"Invalid GameDirectory specified in appsettings.{Settings.Product}.json"); + DoExit(); + } + + string buildInfoPath = Path.Combine(env.WebRootPath, "SystemFiles", ".build.info"); + + //.build.info import + ImportBuildInfo(buildInfoPath); + + //.build.info check + if (!File.Exists(buildInfoPath)) { - Logger.LogCritical("Missing .build.info."); - exit = true; - } + Logger.LogCritical($"Missing .build.info in {Path.Combine(env.WebRootPath, "SystemFiles")}"); + Logger.LogCritical("Open Battle.net Launcher to regenerate it and try again."); + DoExit(); + } //Validate the domain name - must be a valid domain or localhost bool hasProtocol = Settings.HostDomain.ToLowerInvariant().Contains("://"); if (IPAddress.TryParse(Settings.HostDomain, out IPAddress address)) { Logger.LogCritical("HostDomain must be a domain Name."); - exit = true; - } + DoExit(); + } else if (hasProtocol || !Regex.IsMatch(Settings.HostDomain, DOMAIN_REGEX + "$", RegexOptions.IgnoreCase)) { string domain = Regex.Match(Settings.HostDomain, DOMAIN_REGEX, RegexOptions.IgnoreCase).Groups[1].Value; @@ -104,20 +180,13 @@ private void StartUpChecks(IHostingEnvironment env) if (!Uri.IsWellFormedUriString(Settings.PatchUrl, UriKind.Absolute)) { Logger.LogCritical("Malformed Patch Url."); - exit = true; - } + DoExit(); + } else if (!PingPatchUrl()) { Logger.LogCritical("Unreachable Patch Url."); - exit = true; - } - - if (exit) - { - Logger.LogCritical("Exiting..."); - System.Threading.Thread.Sleep(3000); - Environment.Exit(0); - } + DoExit(); + } } private bool PingPatchUrl() @@ -125,7 +194,7 @@ private bool PingPatchUrl() try { using (var clientHandler = new HttpClientHandler() { AllowAutoRedirect = false }) - using (var webRequest = new HttpClient(clientHandler) { Timeout = TimeSpan.FromSeconds(3) }) + using (var webRequest = new HttpClient(clientHandler) { Timeout = TimeSpan.FromSeconds(10) }) using (var request = new HttpRequestMessage(HttpMethod.Head, Settings.PatchUrl + "/versions")) using (var response = webRequest.SendAsync(request).Result) return response.StatusCode == HttpStatusCode.OK; @@ -135,5 +204,12 @@ private bool PingPatchUrl() return false; } } + + private void DoExit() + { + Logger.LogCritical("Exiting..."); + System.Threading.Thread.Sleep(3000); + Environment.Exit(0); + } } } diff --git a/CASCHost/appsettings.json b/CASCHost/appsettings.json deleted file mode 100644 index 320998f..0000000 --- a/CASCHost/appsettings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "AppSettings": { - "MinimumFileDataId": 2500000, - "OnlineListFile": false, - "BNetAppSupport": false, - "StaticMode": false, - "RebuildPassword": "", - "HostDomain": "localhost:5100", - "CDNs": [], - "SqlConnection": "server=127.0.0.1;uid=root;pwd=;database=caschost", - "PatchUrl": "http://us.patch.battle.net:1119/wow", - "Locale": "enus", - "DirectoryHash": [ - "0aa8acdad03fa93a760d6a5824e1f0a3", - "89a7085aa643b8a200eef77c25d20439" - ] - } -} \ No newline at end of file diff --git a/CASCHost/appsettings.wow.json b/CASCHost/appsettings.wow.json new file mode 100644 index 0000000..a7daa57 --- /dev/null +++ b/CASCHost/appsettings.wow.json @@ -0,0 +1,21 @@ +{ + "AppSettings": { + "MinimumFileDataId": 2500000, + "OnlineListFile": true, + "BNetAppSupport": false, + "StaticMode": false, + "RebuildPassword": "", + "GameDirectory": "", + "Product": "wow", + "SqliteDatabase": "caschost.wow.db3", + "HostDomain": "localhost:5100", + "CDNs": [], + "PatchUrl": "http://real-en.patch.battle.net:1119/wow", + "Locale": "engb", + "DirectoryHash": [ + "", + "" + ], + "GameVersion": "" + } +} \ No newline at end of file diff --git a/CASCHost/appsettings.wow_classic.json b/CASCHost/appsettings.wow_classic.json new file mode 100644 index 0000000..a9446c9 --- /dev/null +++ b/CASCHost/appsettings.wow_classic.json @@ -0,0 +1,21 @@ +{ + "AppSettings": { + "MinimumFileDataId": 2500000, + "OnlineListFile": true, + "BNetAppSupport": false, + "StaticMode": false, + "RebuildPassword": "", + "GameDirectory": "", + "Product": "wow_classic", + "SqliteDatabase": "caschost.wow_classic.db3", + "HostDomain": "localhost:5101", + "CDNs": [], + "PatchUrl": "http://real-en.patch.battle.net:1119/wow", + "Locale": "engb", + "DirectoryHash": [ + "", + "" + ], + "GameVersion": "" + } +} \ No newline at end of file diff --git a/CASCHost/hosting.json b/CASCHost/hosting.wow.json similarity index 100% rename from CASCHost/hosting.json rename to CASCHost/hosting.wow.json diff --git a/CASCHost/hosting.wow_classic.json b/CASCHost/hosting.wow_classic.json new file mode 100644 index 0000000..385df91 --- /dev/null +++ b/CASCHost/hosting.wow_classic.json @@ -0,0 +1,3 @@ +{ + "urls": "http://localhost:5101;http://0.0.0.0:5101;http://*:5101" +} \ No newline at end of file diff --git a/HttpProxy/conf/fastcgi.conf b/HttpProxy/conf/fastcgi.conf new file mode 100644 index 0000000..091738c --- /dev/null +++ b/HttpProxy/conf/fastcgi.conf @@ -0,0 +1,26 @@ + +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/HttpProxy/conf/fastcgi_params b/HttpProxy/conf/fastcgi_params new file mode 100644 index 0000000..28decb9 --- /dev/null +++ b/HttpProxy/conf/fastcgi_params @@ -0,0 +1,25 @@ + +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/HttpProxy/conf/koi-utf b/HttpProxy/conf/koi-utf new file mode 100644 index 0000000..e7974ff --- /dev/null +++ b/HttpProxy/conf/koi-utf @@ -0,0 +1,109 @@ + +# This map is not a full koi8-r <> utf8 map: it does not contain +# box-drawing and some other characters. Besides this map contains +# several koi8-u and Byelorussian letters which are not in koi8-r. +# If you need a full and standard map, use contrib/unicode2nginx/koi-utf +# map instead. + +charset_map koi8-r utf-8 { + + 80 E282AC ; # euro + + 95 E280A2 ; # bullet + + 9A C2A0 ; #   + + 9E C2B7 ; # · + + A3 D191 ; # small yo + A4 D194 ; # small Ukrainian ye + + A6 D196 ; # small Ukrainian i + A7 D197 ; # small Ukrainian yi + + AD D291 ; # small Ukrainian soft g + AE D19E ; # small Byelorussian short u + + B0 C2B0 ; # ° + + B3 D081 ; # capital YO + B4 D084 ; # capital Ukrainian YE + + B6 D086 ; # capital Ukrainian I + B7 D087 ; # capital Ukrainian YI + + B9 E28496 ; # numero sign + + BD D290 ; # capital Ukrainian soft G + BE D18E ; # capital Byelorussian short U + + BF C2A9 ; # (C) + + C0 D18E ; # small yu + C1 D0B0 ; # small a + C2 D0B1 ; # small b + C3 D186 ; # small ts + C4 D0B4 ; # small d + C5 D0B5 ; # small ye + C6 D184 ; # small f + C7 D0B3 ; # small g + C8 D185 ; # small kh + C9 D0B8 ; # small i + CA D0B9 ; # small j + CB D0BA ; # small k + CC D0BB ; # small l + CD D0BC ; # small m + CE D0BD ; # small n + CF D0BE ; # small o + + D0 D0BF ; # small p + D1 D18F ; # small ya + D2 D180 ; # small r + D3 D181 ; # small s + D4 D182 ; # small t + D5 D183 ; # small u + D6 D0B6 ; # small zh + D7 D0B2 ; # small v + D8 D18C ; # small soft sign + D9 D18B ; # small y + DA D0B7 ; # small z + DB D188 ; # small sh + DC D18D ; # small e + DD D189 ; # small shch + DE D187 ; # small ch + DF D18A ; # small hard sign + + E0 D0AE ; # capital YU + E1 D090 ; # capital A + E2 D091 ; # capital B + E3 D0A6 ; # capital TS + E4 D094 ; # capital D + E5 D095 ; # capital YE + E6 D0A4 ; # capital F + E7 D093 ; # capital G + E8 D0A5 ; # capital KH + E9 D098 ; # capital I + EA D099 ; # capital J + EB D09A ; # capital K + EC D09B ; # capital L + ED D09C ; # capital M + EE D09D ; # capital N + EF D09E ; # capital O + + F0 D09F ; # capital P + F1 D0AF ; # capital YA + F2 D0A0 ; # capital R + F3 D0A1 ; # capital S + F4 D0A2 ; # capital T + F5 D0A3 ; # capital U + F6 D096 ; # capital ZH + F7 D092 ; # capital V + F8 D0AC ; # capital soft sign + F9 D0AB ; # capital Y + FA D097 ; # capital Z + FB D0A8 ; # capital SH + FC D0AD ; # capital E + FD D0A9 ; # capital SHCH + FE D0A7 ; # capital CH + FF D0AA ; # capital hard sign +} diff --git a/HttpProxy/conf/koi-win b/HttpProxy/conf/koi-win new file mode 100644 index 0000000..72afabe --- /dev/null +++ b/HttpProxy/conf/koi-win @@ -0,0 +1,103 @@ + +charset_map koi8-r windows-1251 { + + 80 88 ; # euro + + 95 95 ; # bullet + + 9A A0 ; #   + + 9E B7 ; # · + + A3 B8 ; # small yo + A4 BA ; # small Ukrainian ye + + A6 B3 ; # small Ukrainian i + A7 BF ; # small Ukrainian yi + + AD B4 ; # small Ukrainian soft g + AE A2 ; # small Byelorussian short u + + B0 B0 ; # ° + + B3 A8 ; # capital YO + B4 AA ; # capital Ukrainian YE + + B6 B2 ; # capital Ukrainian I + B7 AF ; # capital Ukrainian YI + + B9 B9 ; # numero sign + + BD A5 ; # capital Ukrainian soft G + BE A1 ; # capital Byelorussian short U + + BF A9 ; # (C) + + C0 FE ; # small yu + C1 E0 ; # small a + C2 E1 ; # small b + C3 F6 ; # small ts + C4 E4 ; # small d + C5 E5 ; # small ye + C6 F4 ; # small f + C7 E3 ; # small g + C8 F5 ; # small kh + C9 E8 ; # small i + CA E9 ; # small j + CB EA ; # small k + CC EB ; # small l + CD EC ; # small m + CE ED ; # small n + CF EE ; # small o + + D0 EF ; # small p + D1 FF ; # small ya + D2 F0 ; # small r + D3 F1 ; # small s + D4 F2 ; # small t + D5 F3 ; # small u + D6 E6 ; # small zh + D7 E2 ; # small v + D8 FC ; # small soft sign + D9 FB ; # small y + DA E7 ; # small z + DB F8 ; # small sh + DC FD ; # small e + DD F9 ; # small shch + DE F7 ; # small ch + DF FA ; # small hard sign + + E0 DE ; # capital YU + E1 C0 ; # capital A + E2 C1 ; # capital B + E3 D6 ; # capital TS + E4 C4 ; # capital D + E5 C5 ; # capital YE + E6 D4 ; # capital F + E7 C3 ; # capital G + E8 D5 ; # capital KH + E9 C8 ; # capital I + EA C9 ; # capital J + EB CA ; # capital K + EC CB ; # capital L + ED CC ; # capital M + EE CD ; # capital N + EF CE ; # capital O + + F0 CF ; # capital P + F1 DF ; # capital YA + F2 D0 ; # capital R + F3 D1 ; # capital S + F4 D2 ; # capital T + F5 D3 ; # capital U + F6 C6 ; # capital ZH + F7 C2 ; # capital V + F8 DC ; # capital soft sign + F9 DB ; # capital Y + FA C7 ; # capital Z + FB D8 ; # capital SH + FC DD ; # capital E + FD D9 ; # capital SHCH + FE D7 ; # capital CH + FF DA ; # capital hard sign +} diff --git a/HttpProxy/conf/mime.types b/HttpProxy/conf/mime.types new file mode 100644 index 0000000..2961256 --- /dev/null +++ b/HttpProxy/conf/mime.types @@ -0,0 +1,97 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/HttpProxy/conf/nginx.conf b/HttpProxy/conf/nginx.conf new file mode 100644 index 0000000..947f9cc --- /dev/null +++ b/HttpProxy/conf/nginx.conf @@ -0,0 +1,93 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + map_hash_bucket_size 64; + map $server_name $real_host { + default 0; + + real-en.patch.battle.net eu.patch.battle.net; + real-us.patch.battle.net us.patch.battle.net; + real-cn.patch.battle.net cn.patch.battle.net; + } + + + server_names_hash_bucket_size 64; + + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + resolver 114.114.114.114; + resolver_timeout 10s; + + + server { + listen 1119; + + server_name eu.patch.battle.net us.patch.battle.net cn.patch.battle.net; + set $backend $server_name; + + location / { + proxy_set_header Host $host; + proxy_pass http://$backend:1119; + } + + location /wow/cdns { + proxy_set_header Host $host; + proxy_pass http://127.0.0.1:5100/wow/cdns; + } + + location /wow/versions { + proxy_set_header Host $host; + proxy_pass http://127.0.0.1:5100/wow/versions; + } + + location /wow_classic/cdns { + proxy_set_header Host $host; + proxy_pass http://127.0.0.1:5101/wow/cdns; + } + + location /wow_classic/versions { + proxy_set_header Host $host; + proxy_pass http://127.0.0.1:5101/wow/versions; + } + } + + server { + listen 1119; + + server_name real-en.patch.battle.net real-us.patch.battle.net real-cn.patch.battle.net; + set $backend $real_host; + + location / { + proxy_set_header Host $host; + proxy_pass http://$backend:1119; + } + } + +} diff --git a/HttpProxy/conf/scgi_params b/HttpProxy/conf/scgi_params new file mode 100644 index 0000000..6d4ce4f --- /dev/null +++ b/HttpProxy/conf/scgi_params @@ -0,0 +1,17 @@ + +scgi_param REQUEST_METHOD $request_method; +scgi_param REQUEST_URI $request_uri; +scgi_param QUERY_STRING $query_string; +scgi_param CONTENT_TYPE $content_type; + +scgi_param DOCUMENT_URI $document_uri; +scgi_param DOCUMENT_ROOT $document_root; +scgi_param SCGI 1; +scgi_param SERVER_PROTOCOL $server_protocol; +scgi_param REQUEST_SCHEME $scheme; +scgi_param HTTPS $https if_not_empty; + +scgi_param REMOTE_ADDR $remote_addr; +scgi_param REMOTE_PORT $remote_port; +scgi_param SERVER_PORT $server_port; +scgi_param SERVER_NAME $server_name; diff --git a/HttpProxy/conf/uwsgi_params b/HttpProxy/conf/uwsgi_params new file mode 100644 index 0000000..09c732c --- /dev/null +++ b/HttpProxy/conf/uwsgi_params @@ -0,0 +1,17 @@ + +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param REQUEST_SCHEME $scheme; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/HttpProxy/conf/win-utf b/HttpProxy/conf/win-utf new file mode 100644 index 0000000..ed8bc00 --- /dev/null +++ b/HttpProxy/conf/win-utf @@ -0,0 +1,126 @@ + +# This map is not a full windows-1251 <> utf8 map: it does not +# contain Serbian and Macedonian letters. If you need a full map, +# use contrib/unicode2nginx/win-utf map instead. + +charset_map windows-1251 utf-8 { + + 82 E2809A ; # single low-9 quotation mark + + 84 E2809E ; # double low-9 quotation mark + 85 E280A6 ; # ellipsis + 86 E280A0 ; # dagger + 87 E280A1 ; # double dagger + 88 E282AC ; # euro + 89 E280B0 ; # per mille + + 91 E28098 ; # left single quotation mark + 92 E28099 ; # right single quotation mark + 93 E2809C ; # left double quotation mark + 94 E2809D ; # right double quotation mark + 95 E280A2 ; # bullet + 96 E28093 ; # en dash + 97 E28094 ; # em dash + + 99 E284A2 ; # trade mark sign + + A0 C2A0 ; #   + A1 D18E ; # capital Byelorussian short U + A2 D19E ; # small Byelorussian short u + + A4 C2A4 ; # currency sign + A5 D290 ; # capital Ukrainian soft G + A6 C2A6 ; # borken bar + A7 C2A7 ; # section sign + A8 D081 ; # capital YO + A9 C2A9 ; # (C) + AA D084 ; # capital Ukrainian YE + AB C2AB ; # left-pointing double angle quotation mark + AC C2AC ; # not sign + AD C2AD ; # soft hypen + AE C2AE ; # (R) + AF D087 ; # capital Ukrainian YI + + B0 C2B0 ; # ° + B1 C2B1 ; # plus-minus sign + B2 D086 ; # capital Ukrainian I + B3 D196 ; # small Ukrainian i + B4 D291 ; # small Ukrainian soft g + B5 C2B5 ; # micro sign + B6 C2B6 ; # pilcrow sign + B7 C2B7 ; # · + B8 D191 ; # small yo + B9 E28496 ; # numero sign + BA D194 ; # small Ukrainian ye + BB C2BB ; # right-pointing double angle quotation mark + + BF D197 ; # small Ukrainian yi + + C0 D090 ; # capital A + C1 D091 ; # capital B + C2 D092 ; # capital V + C3 D093 ; # capital G + C4 D094 ; # capital D + C5 D095 ; # capital YE + C6 D096 ; # capital ZH + C7 D097 ; # capital Z + C8 D098 ; # capital I + C9 D099 ; # capital J + CA D09A ; # capital K + CB D09B ; # capital L + CC D09C ; # capital M + CD D09D ; # capital N + CE D09E ; # capital O + CF D09F ; # capital P + + D0 D0A0 ; # capital R + D1 D0A1 ; # capital S + D2 D0A2 ; # capital T + D3 D0A3 ; # capital U + D4 D0A4 ; # capital F + D5 D0A5 ; # capital KH + D6 D0A6 ; # capital TS + D7 D0A7 ; # capital CH + D8 D0A8 ; # capital SH + D9 D0A9 ; # capital SHCH + DA D0AA ; # capital hard sign + DB D0AB ; # capital Y + DC D0AC ; # capital soft sign + DD D0AD ; # capital E + DE D0AE ; # capital YU + DF D0AF ; # capital YA + + E0 D0B0 ; # small a + E1 D0B1 ; # small b + E2 D0B2 ; # small v + E3 D0B3 ; # small g + E4 D0B4 ; # small d + E5 D0B5 ; # small ye + E6 D0B6 ; # small zh + E7 D0B7 ; # small z + E8 D0B8 ; # small i + E9 D0B9 ; # small j + EA D0BA ; # small k + EB D0BB ; # small l + EC D0BC ; # small m + ED D0BD ; # small n + EE D0BE ; # small o + EF D0BF ; # small p + + F0 D180 ; # small r + F1 D181 ; # small s + F2 D182 ; # small t + F3 D183 ; # small u + F4 D184 ; # small f + F5 D185 ; # small kh + F6 D186 ; # small ts + F7 D187 ; # small ch + F8 D188 ; # small sh + F9 D189 ; # small shch + FA D18A ; # small hard sign + FB D18B ; # small y + FC D18C ; # small soft sign + FD D18D ; # small e + FE D18E ; # small yu + FF D18F ; # small ya +} diff --git a/HttpProxy/html/50x.html b/HttpProxy/html/50x.html new file mode 100644 index 0000000..9071e0a --- /dev/null +++ b/HttpProxy/html/50x.html @@ -0,0 +1,21 @@ + + + +Error + + + +

An error occurred.

+

Sorry, the page you are looking for is currently unavailable.
+Please try again later.

+

If you are the system administrator of this resource then you should check +the error log for details.

+

Faithfully yours, nginx.

+ + diff --git a/HttpProxy/html/index.html b/HttpProxy/html/index.html new file mode 100644 index 0000000..2ca3b95 --- /dev/null +++ b/HttpProxy/html/index.html @@ -0,0 +1,25 @@ + + + +Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and +working. Further configuration is required.

+ +

For online documentation and support please refer to +nginx.org.
+Commercial support is available at +nginx.com.

+ +

Thank you for using nginx.

+ + diff --git a/HttpProxy/start.bat b/HttpProxy/start.bat new file mode 100644 index 0000000..1117a7d --- /dev/null +++ b/HttpProxy/start.bat @@ -0,0 +1,5 @@ +start nginx.exe +pause + +nginx.exe -s quit +pause \ No newline at end of file diff --git a/HttpProxy/stop.bat b/HttpProxy/stop.bat new file mode 100644 index 0000000..d98e174 --- /dev/null +++ b/HttpProxy/stop.bat @@ -0,0 +1,2 @@ +nginx.exe -s quit +pause \ No newline at end of file