diff --git a/Build/Data/WebGUI.js b/Build/Data/WebGUI.js new file mode 100644 index 0000000..e5ff903 --- /dev/null +++ b/Build/Data/WebGUI.js @@ -0,0 +1,394 @@ +CMSDesktop = {}; +CMSDesktop.startDrag = function(){} +var callbackFuncs = []; +var ignore_callFunc = ["QueryFirmwareUpdateInfo", "GetGameList"]; +var ignore_onFunc = ["onUpdateFirmwareMessage"]; +function makeRequest(data) +{ + data.token = "UNIQUE_TOKEN_GOES_HERE"; + + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() + { + if (xhr.readyState == XMLHttpRequest.DONE) + { + var response = xhr.responseText; + //console.log("response: " + xhr.responseText); + if (response.length == 0) + { + if (data.onFailure != null) + { + console.log(data); + var additionalInfo = ""; + if (data.request != null) + { + var reqInfo = JSON.parse(data.request); + if (reqInfo.funcname != null && !ignore_callFunc.includes(reqInfo.funcname)) + { + data.onFailure(0, "fail - " + reqInfo.funcname); + } + } + else + { + data.onFailure(0, "fail"); + } + } + } + else + { + if (data.onSuccess != null) + { + data.onSuccess(response); + } + } + } + } + xhr.open("POST", "http://" + window.location.host + "/cms_" + data.requestType, true); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.send(JSON.stringify(data)); +} +window.onFunc = function(name, onFunc) +{ + callbackFuncs[name] = onFunc; + if (!ignore_onFunc.includes(name)) + { + console.log("onFunc: " + name + " " + onFunc); + } +}; +window.callFunc = function(data) +{ + //console.log(data); + var handled = false; + if (data.request != null) + { + handled = true; + var reqInfo = JSON.parse(data.request); + switch (reqInfo.funcname) + { + case "SetRecordBtn": + { + isHoveringRecordBtn = reqInfo.SetRecordBtn; + } + break; + case "StartRecord": + { + isRecording = true; + } + break; + case "StopRecord": + { + isRecording = true; + } + break; + default: + handled = false; + break; + } + } + if (!handled) + { + data.requestType = "callFunc"; + makeRequest(data); + } +}; +var isRecording = false; +var isHoveringRecordBtn = false; +var keyStates = []; +var keyNameMap = []; +keyNameMap["Escape"] = "Esc"; +keyNameMap["F1"] = "F1"; +keyNameMap["F2"] = "F2"; +keyNameMap["F3"] = "F3"; +keyNameMap["F4"] = "F4"; +keyNameMap["F5"] = "F5"; +keyNameMap["F6"] = "F6"; +keyNameMap["F7"] = "F7"; +keyNameMap["F8"] = "F8"; +keyNameMap["F9"] = "F9"; +keyNameMap["F10"] = "F10"; +keyNameMap["F11"] = "F11"; +keyNameMap["F12"] = "F12"; +keyNameMap["PrintScreen"] = "Print Screen"; +keyNameMap["ScrollLock"] = "Scroll Lock"; +keyNameMap["Pause"] = "Pause"; +keyNameMap["Backquote"] = "`"; +keyNameMap["Digit1"] = "1"; +keyNameMap["Digit2"] = "2"; +keyNameMap["Digit3"] = "3"; +keyNameMap["Digit4"] = "4"; +keyNameMap["Digit5"] = "5"; +keyNameMap["Digit6"] = "6"; +keyNameMap["Digit7"] = "7"; +keyNameMap["Digit8"] = "8"; +keyNameMap["Digit9"] = "9"; +keyNameMap["Digit0"] = "0"; +keyNameMap["Minus"] = "-"; +keyNameMap["Equal"] = "="; +keyNameMap["Backspace"] = "Backspace"; +keyNameMap["Insert"] = "Insert"; +keyNameMap["Home"] = "Home"; +keyNameMap["PageUp"] = "Page Up"; +keyNameMap["Tab"] = "Tab"; +keyNameMap["KeyQ"] = "Q"; +keyNameMap["KeyW"] = "W"; +keyNameMap["KeyE"] = "E"; +keyNameMap["KeyR"] = "R"; +keyNameMap["KeyT"] = "T"; +keyNameMap["KeyY"] = "Y"; +keyNameMap["KeyU"] = "U"; +keyNameMap["KeyI"] = "I"; +keyNameMap["KeyO"] = "O"; +keyNameMap["KeyP"] = "P"; +keyNameMap["BracketLeft"] = "["; +keyNameMap["BracketRight"] = "]"; +keyNameMap["Backslash"] = "\\"; +keyNameMap["Delete"] = "Delete"; +keyNameMap["End"] = "End"; +keyNameMap["PageDown"] = "Page Down"; +keyNameMap["CapsLock"] = "Caps Lock"; +keyNameMap["KeyA"] = "A"; +keyNameMap["KeyS"] = "S"; +keyNameMap["KeyD"] = "D"; +keyNameMap["KeyF"] = "F"; +keyNameMap["KeyG"] = "G"; +keyNameMap["KeyH"] = "H"; +keyNameMap["KeyJ"] = "J"; +keyNameMap["KeyK"] = "K"; +keyNameMap["KeyL"] = "L"; +keyNameMap["Semicolon"] = ";"; +keyNameMap["Quote"] = "'"; +keyNameMap["Enter"] = "Enter"; +keyNameMap["ShiftLeft"] = "Left Shift"; +keyNameMap["IntlBackslash"] = "AltBackslash"; +keyNameMap["KeyZ"] = "Z"; +keyNameMap["KeyX"] = "X"; +keyNameMap["KeyC"] = "C"; +keyNameMap["KeyV"] = "V"; +keyNameMap["KeyB"] = "B"; +keyNameMap["KeyN"] = "N"; +keyNameMap["KeyM"] = "M"; +keyNameMap["Comma"] = ","; +keyNameMap["Period"] = "."; +keyNameMap["Slash"] = "/"; +keyNameMap["ShiftRight"] = "Right Shift"; +keyNameMap["ArrowUp"] = "Up"; +keyNameMap["ControlLeft"] = "Left Ctrl"; +keyNameMap["MetaLeft"] = "Left Win"; +keyNameMap["AltLeft"] = "Left Alt"; +keyNameMap["Space"] = "Space"; +keyNameMap["AltRight"] = "Right Alt"; +keyNameMap["MetaRight"] = "Right Win"; +keyNameMap["ContextMenu"] = "Menu"; +keyNameMap["ControlRight"] = "Right Ctrl"; +keyNameMap["NumLock"] = "Num Lock"; +keyNameMap["NumpadDivide"] = "Num /"; +keyNameMap["NumpadMultiply"] = "Num *"; +keyNameMap["NumpadSubtract"] = "Num -"; +keyNameMap["Numpad7"] = "Num 7"; +keyNameMap["Numpad8"] = "Num 8"; +keyNameMap["Numpad9"] = "Num 9"; +keyNameMap["NumpadAdd"] = "Num +"; +keyNameMap["Numpad4"] = "Num 4"; +keyNameMap["Numpad5"] = "Num 5"; +keyNameMap["Numpad6"] = "Num 6"; +keyNameMap["Numpad1"] = "Num 1"; +keyNameMap["Numpad2"] = "Num 2"; +keyNameMap["Numpad3"] = "Num 3"; +keyNameMap["Numpad0"] = "Num 0"; +keyNameMap["NumpadDecimal"] = "Num ."; +keyNameMap["NumpadEnter"] = "Num Enter"; +keyNameMap["LaunchMediaPlayer"] = "OpenMediaPlayer"; +keyNameMap["MediaPlayPause"] = "MediaPlayPause"; +keyNameMap["MediaStop"] = "MediaStop"; +keyNameMap["MediaTrackPrevious"] = "MediaPrevious"; +keyNameMap["MediaTrackNext"] = "MediaNext"; +keyNameMap["AudioVolumeUp"] = "VolumeUp"; +keyNameMap["AudioVolumeDown"] = "VolumeDown"; +keyNameMap["AudioVolumeMute"] = "VolumeMute"; +keyNameMap["BrowserStop"] = "BrowserStop"; +keyNameMap["BrowserBack"] = "BrowserBack"; +keyNameMap["BrowserForward"] = "BrowserForward"; +keyNameMap["BrowserRefresh"] = "BrowserRefresh"; +keyNameMap["BrowserFavorites"] = "BrowserFavorites"; +keyNameMap["BrowserHome"] = "BrowserHome"; +keyNameMap["LaunchMail"] = "OpenEmail"; +keyNameMap["LaunchApplication1"] = "OpenMyComputer"; +keyNameMap["LaunchApplication2"] = "OpenCalculator"; +keyNameMap["NumpadEqual"] = "Clear"; +keyNameMap["F13"] = "F13"; +keyNameMap["F14"] = "F14"; +keyNameMap["F15"] = "F15"; +keyNameMap["F16"] = "F16"; +keyNameMap["F17"] = "F17"; +keyNameMap["F18"] = "F18"; +keyNameMap["F19"] = "F19"; +keyNameMap["F20"] = "F20"; +keyNameMap["F21"] = "F21"; +keyNameMap["F22"] = "F22"; +keyNameMap["F23"] = "F23"; +keyNameMap["F24"] = "F24"; +keyNameMap["NumpadComma"] = "NumpadComma"; +keyNameMap["IntlRo"] = "IntlRo"; +keyNameMap["KanaMode"] = "KanaMode"; +keyNameMap["IntlYen"] = "IntlYen"; +keyNameMap["Convert"] = "Convert"; +keyNameMap["NonConvert"] = "NonConvert"; +keyNameMap["Lang3"] = "Lang3"; +keyNameMap["Lang4"] = "Lang4"; +function beginTimer() +{ + return new Date(); +} +function endTimer(startTime) +{ + endTime = new Date(); + return endTime - startTime; +} +window.onkeydown = function(e) +{ + var code = e.code != null ? e.code : e.key; + if (!isRecording || keyNameMap[code] == null) + { + return; + } + var keyName = keyNameMap[code]; + var state = keyStates[code]; + if (state == null) + { + state = keyStates[code] = {}; + } + if (state.down) + { + return; + } + state.down = true; + state.time = beginTimer(); + var func = callbackFuncs["onKeyDown"]; + if (func != null) + { + var data = []; + data[0] = "\"" + keyName + "\""; + func(data); + } +}; +window.onkeyup = function(e) +{ + var code = e.code != null ? e.code : e.key; + if (!isRecording || keyNameMap[code] == null) + { + return; + } + var keyName = keyNameMap[code]; + var state = keyStates[code]; + if (state == null || !state.down) + { + return; + } + state.down = false; + var duration = endTimer(state.time); + if (duration > 0) + { + var delayFunc = callbackFuncs["onDelay"]; + if (delayFunc != null) + { + var delayData = []; + delayData[0] = duration; + delayFunc(delayData); + } + } + var func = callbackFuncs["onKeyUp"]; + if (func != null) + { + var data = []; + data[0] = "\"" + keyName + "\""; + func(data); + } +}; +window.onmousedown = function(e) +{ + if (!isRecording || isHoveringRecordBtn) + { + return; + } + var state = keyStates[e.button]; + if (state == null) + { + state = keyStates[e.button] = {}; + } + if (state.down) + { + return; + } + state.down = true; + state.time = beginTimer(); + var func = callbackFuncs["onMouseDown"]; + if (func != null) + { + var data = []; + data[0] = e.button; + func(data); + } +}; +window.onmouseup = function(e) +{ + if (!isRecording || isHoveringRecordBtn) + { + return; + } + var state = keyStates[e.button]; + if (state == null || !state.down) + { + return; + } + state.down = false; + var duration = endTimer(state.time); + if (duration > 0) + { + var delayFunc = callbackFuncs["onDelay"]; + if (delayFunc != null) + { + var delayData = []; + delayData[0] = duration; + delayFunc(delayData); + } + } + var func = callbackFuncs["onMouseUp"]; + if (func != null) + { + var data = []; + data[0] = e.button; + func(data); + } +}; +function S4() +{ + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); +} + +window.getGuid = function() +{ + return (S4() + S4() + "-" + S4() + "-4" + S4().substr(0,3) + "-" + S4() + "-" + S4() + S4() + S4()).toUpperCase(); +}; +function ping() +{ + var data = {}; + data.requestType = "ping"; + data.onSuccess = function(response) + { + var messages = JSON.parse(response); + if (messages != null && messages.length > 0) + { + for (var i = 0; i < messages.length; i++) + { + var callback = callbackFuncs[messages[i].funcName]; + if (callback != null) + { + callback(messages[i].data); + } + } + } + }; + makeRequest(data); + setTimeout(ping, 1000); +} +ping(); \ No newline at end of file diff --git a/Build/UserData/Sample.txt b/Build/UserData/Sample.txt index 358e9b9..10f348e 100644 --- a/Build/UserData/Sample.txt +++ b/Build/UserData/Sample.txt @@ -64,6 +64,9 @@ Q:Macro(QuickCutSave) # This uses static lighting to set the 'ASD' keys to red and 'W' to green. See the "WASD static.le" file to see how this works. [Lighting(WASD static,Layer1)] +# This clears previously assigned lighting effects (it doesn't clear any lighting effects set in this file) +[NoLighting] + # If you're confused about the codes/numbers in the lighting effect files, use the command "dumpkeys null ex" to see the codes for your keyboard keys. # When creating your own lighting effects you can use the names of the keys as seen in the link at the top of this file. diff --git a/GK6X/CMFile.cs b/GK6X/CMFile.cs index 64d4ffa..9ea960d 100644 --- a/GK6X/CMFile.cs +++ b/GK6X/CMFile.cs @@ -50,6 +50,19 @@ static class CMFile const uint fileSignature = 0x434D4631;// Magic / signature "1FMC" + // This is a hack... these values are wack, no idea what's going on with them + static Dictionary unknownMaps = new Dictionary() + { + { GetUnknownMapStr("ihds"), 25869 }, + { GetUnknownMapStr("IHDS"), 3155 }, + { 0xA7D0DECE, 36218 },// "Unknown" + }; + + private static uint GetUnknownMapStr(string header) + { + return BitConverter.ToUInt32(Encoding.ASCII.GetBytes(header), 0); + } + public static byte[] Load(string path) { if (File.Exists(path)) @@ -99,9 +112,24 @@ private static byte[] Decrypt(byte[] buffer, string file) // File type (string) is at offset 24, written as 8 bytes, padded with 00 stream.Position = 24; byte[] fileTypeStrBuffer = reader.ReadBytes(8); + uint intFileType = BitConverter.ToUInt32(fileTypeStrBuffer, 0); + if (unknownMaps.ContainsKey(intFileType)) + { + byte[] newType = BitConverter.GetBytes(unknownMaps[intFileType]); + fileTypeStrBuffer = new byte[8]; + Buffer.BlockCopy(newType, 0, fileTypeStrBuffer, 0, newType.Length); + } + List blob = new List(); + for (int i = 0; i < fileTypeStrBuffer.Length; i++) + { + if (fileTypeStrBuffer[i] == 0) + { + break; + } + blob.Add(fileTypeStrBuffer[i]); + } // First crc the file type name, then get crc the file type name (including zeroed bytes) - string fileTypeStr = Encoding.ASCII.GetString(fileTypeStrBuffer).TrimEnd('\0'); - ushort encryptionKey = Crc16.GetCrc(Encoding.ASCII.GetBytes(fileTypeStr)); + ushort encryptionKey = Crc16.GetCrc(blob.ToArray()); encryptionKey = Crc16.GetCrc(fileTypeStrBuffer, 0, encryptionKey); // Data is at offset 32 @@ -140,7 +168,8 @@ public static byte[] Encrypt(byte[] fileData, CMFileType fileType) using (MemoryStream stream = new MemoryStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { - byte[] fileTypeStrBuffer = fileTypes[fileType]; + byte[] fileTypeStrBuffer = new byte[8]; + Buffer.BlockCopy(fileTypes[fileType], 0, fileTypeStrBuffer, 0, fileTypes[fileType].Length); string fileTypeStr = Encoding.ASCII.GetString(fileTypeStrBuffer).TrimEnd('\0'); ushort encryptionKey = Crc16.GetCrc(Encoding.ASCII.GetBytes(fileTypeStr)); encryptionKey = Crc16.GetCrc(fileTypeStrBuffer, 0, encryptionKey); diff --git a/GK6X/GK6X.csproj b/GK6X/GK6X.csproj index a159eec..c187c88 100644 --- a/GK6X/GK6X.csproj +++ b/GK6X/GK6X.csproj @@ -79,6 +79,7 @@ + \ No newline at end of file diff --git a/GK6X/KeyValues.cs b/GK6X/KeyValues.cs index 14d918b..b4a60c9 100644 --- a/GK6X/KeyValues.cs +++ b/GK6X/KeyValues.cs @@ -570,4 +570,187 @@ public enum DriverValueMouseButton Back = 0x08, Advance = 0x10 } + + internal static class MacroKeyNames + { + public struct Item + { + public string Name; + public uint DriverValue; + public string JsCode;// javascript code + } + + public static Dictionary Names = new Dictionary(); + + static MacroKeyNames() + { + // CANT_MAP = no mapping in the official software (provide our own values) + + // Ordered based on the order in DriverValue + Add("Esc", DriverValue.Esc, "Escape"); + Add("F1", DriverValue.F1, "F1"); + Add("F2", DriverValue.F2, "F2"); + Add("F3", DriverValue.F3, "F3"); + Add("F4", DriverValue.F4, "F4"); + Add("F5", DriverValue.F5, "F5"); + Add("F6", DriverValue.F6, "F6"); + Add("F7", DriverValue.F7, "F7"); + Add("F8", DriverValue.F8, "F8"); + Add("F9", DriverValue.F9, "F9"); + Add("F10", DriverValue.F10, "F10"); + Add("F11", DriverValue.F11, "F11"); + Add("F12", DriverValue.F12, "F12"); + Add("Print Screen", DriverValue.PrintScreen, "PrintScreen"); + Add("Scroll Lock", DriverValue.ScrollLock, "ScrollLock"); + Add("Pause", DriverValue.Pause, "Pause"); + Add("`", DriverValue.BackTick, "Backquote"); + Add("1", DriverValue.D1, "Digit1"); + Add("2", DriverValue.D2, "Digit2"); + Add("3", DriverValue.D3, "Digit3"); + Add("4", DriverValue.D4, "Digit4"); + Add("5", DriverValue.D5, "Digit5"); + Add("6", DriverValue.D6, "Digit6"); + Add("7", DriverValue.D7, "Digit7"); + Add("8", DriverValue.D8, "Digit8"); + Add("9", DriverValue.D9, "Digit9"); + Add("0", DriverValue.D0, "Digit0"); + Add("-", DriverValue.Subtract, "Minus"); + Add("=", DriverValue.Add, "Equal"); + Add("Backspace", DriverValue.Backspace, "Backspace"); + Add("Insert", DriverValue.Insert, "Insert"); + Add("Home", DriverValue.Home, "Home"); + Add("Page Up", DriverValue.PageUp, "PageUp"); + Add("Tab", DriverValue.Tab, "Tab"); + Add("Q", DriverValue.Q, "KeyQ"); + Add("W", DriverValue.W, "KeyW"); + Add("E", DriverValue.E, "KeyE"); + Add("R", DriverValue.R, "KeyR"); + Add("T", DriverValue.T, "KeyT"); + Add("Y", DriverValue.Y, "KeyY"); + Add("U", DriverValue.U, "KeyU"); + Add("I", DriverValue.I, "KeyI"); + Add("O", DriverValue.O, "KeyO"); + Add("P", DriverValue.P, "KeyP"); + Add("[", DriverValue.OpenSquareBrace, "BracketLeft"); + Add("]", DriverValue.CloseSquareBrace, "BracketRight"); + Add("\\", DriverValue.Backslash, "Backslash"); + Add("Delete", DriverValue.Delete, "Delete"); + Add("End", DriverValue.End, "End"); + Add("Page Down", DriverValue.PageDown, "PageDown"); + Add("Caps Lock", DriverValue.CapsLock, "CapsLock"); + Add("A", DriverValue.A, "KeyA"); + Add("S", DriverValue.S, "KeyS"); + Add("D", DriverValue.D, "KeyD"); + Add("F", DriverValue.F, "KeyF"); + Add("G", DriverValue.G, "KeyG"); + Add("H", DriverValue.H, "KeyH"); + Add("J", DriverValue.J, "KeyJ"); + Add("K", DriverValue.K, "KeyK"); + Add("L", DriverValue.L, "KeyL"); + Add(";", DriverValue.Semicolon, "Semicolon"); + Add("'", DriverValue.Quotes, "Quote"); + Add("Enter", DriverValue.Enter, "Enter"); + Add("Left Shift", DriverValue.LShift, "ShiftLeft"); + Add("AltBackslash", DriverValue.AltBackslash, "IntlBackslash");// CANT_MAP + Add("Z", DriverValue.Z, "KeyZ"); + Add("X", DriverValue.X, "KeyX"); + Add("C", DriverValue.C, "KeyC"); + Add("V", DriverValue.V, "KeyV"); + Add("B", DriverValue.B, "KeyB"); + Add("N", DriverValue.N, "KeyN"); + Add("M", DriverValue.M, "KeyM"); + Add(",", DriverValue.Comma, "Comma"); + Add(".", DriverValue.Period, "Period"); + Add("/", DriverValue.Slash, "Slash"); + Add("Right Shift", DriverValue.RShift, "ShiftRight"); + Add("Up", DriverValue.Up, "ArrowUp"); + Add("Left Ctrl", DriverValue.LCtrl, "ControlLeft"); + Add("Left Win", DriverValue.LWin, "MetaLeft"); + Add("Left Alt", DriverValue.LAlt, "AltLeft"); + Add("Space", DriverValue.Space, "Space"); + Add("Right Alt", DriverValue.RAlt, "AltRight"); + Add("Right Win", DriverValue.RWin, "MetaRight"); + Add("Menu", DriverValue.Menu, "ContextMenu");// CANT_MAP + Add("Right Ctrl", DriverValue.RCtrl, "ControlRight"); + Add("Num Lock", DriverValue.NumLock, "NumLock"); + Add("Num /", DriverValue.NumPadSlash, "NumpadDivide"); + Add("Num *", DriverValue.NumPadAsterisk, "NumpadMultiply"); + Add("Num -", DriverValue.NumPadSubtract, "NumpadSubtract"); + Add("Num 7", DriverValue.NumPad7, "Numpad7"); + Add("Num 8", DriverValue.NumPad8, "Numpad8"); + Add("Num 9", DriverValue.NumPad9, "Numpad9"); + Add("Num +", DriverValue.NumPadAdd, "NumpadAdd"); + Add("Num 4", DriverValue.NumPad4, "Numpad4"); + Add("Num 5", DriverValue.NumPad5, "Numpad5"); + Add("Num 6", DriverValue.NumPad6, "Numpad6"); + Add("Num 1", DriverValue.NumPad1, "Numpad1"); + Add("Num 2", DriverValue.NumPad2, "Numpad2"); + Add("Num 3", DriverValue.NumPad3, "Numpad3"); + Add("Num 0", DriverValue.NumPad0, "Numpad0"); + Add("Num .", DriverValue.NumPadPeriod, "NumpadDecimal"); + Add("Num Enter", DriverValue.NumPadEnter, "NumpadEnter");// <--- The official software just maps to regular "Enter" + Add("OpenMediaPlayer", DriverValue.OpenMediaPlayer, "LaunchMediaPlayer");// event.key CANT_MAP + Add("MediaPlayPause", DriverValue.MediaPlayPause, "MediaPlayPause");// event.key CANT_MAP + Add("MediaStop", DriverValue.MediaStop, "MediaStop");// event.key CANT_MAP + Add("MediaPrevious", DriverValue.MediaPrevious, "MediaTrackPrevious");// event.key CANT_MAP + Add("MediaNext", DriverValue.MediaNext, "MediaTrackNext");// event.key CANT_MAP + Add("VolumeUp", DriverValue.VolumeUp, "AudioVolumeUp");// event.key CANT_MAP + Add("VolumeDown", DriverValue.VolumeDown, "AudioVolumeDown");// event.key CANT_MAP + Add("VolumeMute", DriverValue.VolumeMute, "AudioVolumeMute");// event.key CANT_MAP + //Add("BrowserSearch", DriverValue.BrowserSearch, "");// event.key CANT_MAP + Add("BrowserStop", DriverValue.BrowserStop, "BrowserStop");// event.key CANT_MAP + Add("BrowserBack", DriverValue.BrowserBack, "BrowserBack");// event.key CANT_MAP + Add("BrowserForward", DriverValue.BrowserForward, "BrowserForward");// event.key CANT_MAP + Add("BrowserRefresh", DriverValue.BrowserRefresh, "BrowserRefresh");// event.key CANT_MAP + Add("BrowserFavorites", DriverValue.BrowserFavorites, "BrowserFavorites");// event.key CANT_MAP + Add("BrowserHome", DriverValue.BrowserHome, "BrowserHome");// event.key CANT_MAP + Add("OpenEmail", DriverValue.OpenEmail, "LaunchMail");// event.key CANT_MAP + Add("OpenMyComputer", DriverValue.OpenMyComputer, "LaunchApplication1");// event.key CANT_MAP + Add("OpenCalculator", DriverValue.OpenCalculator, "LaunchApplication2");// event.key CANT_MAP + //Add("Copy", DriverValue.Copy, "");// N/A + //Add("Paste", DriverValue.Paste, "");// N/A + //Add("Screenshot", DriverValue.Screenshot, "");// N/A + Add("Clear", DriverValue.Clear, "NumpadEqual"); + Add("F13", DriverValue.F13, "F13"); + Add("F14", DriverValue.F14, "F14"); + Add("F15", DriverValue.F15, "F15"); + Add("F16", DriverValue.F16, "F16"); + Add("F17", DriverValue.F17, "F17"); + Add("F18", DriverValue.F18, "F18"); + Add("F19", DriverValue.F19, "F19"); + Add("F20", DriverValue.F20, "F20"); + Add("F21", DriverValue.F21, "F21"); + Add("F22", DriverValue.F22, "F22"); + Add("F23", DriverValue.F23, "F23"); + Add("F24", DriverValue.F24, "F24"); + Add("NumpadComma", DriverValue.NumPadComma, "NumpadComma");// CANT_MAP + Add("IntlRo", DriverValue.IntlRo, "IntlRo");// CANT_MAP + Add("KanaMode", DriverValue.KanaMode, "KanaMode");// CANT_MAP + Add("IntlYen", DriverValue.IntlYen, "IntlYen");// CANT_MAP + Add("Convert", DriverValue.Convert, "Convert");// CANT_MAP + Add("NonConvert", DriverValue.NonConvert, "NonConvert");// CANT_MAP + Add("Lang3", DriverValue.Lang3, "Lang3");// CANT_MAP + Add("Lang4", DriverValue.Lang4, "Lang4");// CANT_MAP + } + + static void Add(string name, DriverValue value, string jsCode) + { + Names[name] = new Item() + { + Name = name, + DriverValue = (uint)value, + JsCode = jsCode + }; + } + + public static void GenerateJs() + { + StringBuilder sb = new StringBuilder(); + foreach (Item item in Names.Values) + { + sb.AppendLine("keyNameMap[\"" + item.JsCode + "\"] = \"" + item.Name + "\";"); + } + Debug.WriteLine(sb); + } + } } diff --git a/GK6X/KeyboardDevice.cs b/GK6X/KeyboardDevice.cs index c5c2c8e..36322ec 100644 --- a/GK6X/KeyboardDevice.cs +++ b/GK6X/KeyboardDevice.cs @@ -220,10 +220,11 @@ public void SetLighting(KeyboardLayer layer, UserDataFile userData) public void SetMacros(KeyboardLayer layer, UserDataFile userData) { WritePacketNoResponse(OpCodes.LayerResetDataType, (byte)layer, null, (byte)KeyboardLayerDataType.Macros); - if (userData.GetNumMacros(layer) == 0) + // The following check has been disabled so that the WebGUI can assign macros without having to setup the keys + /*if (userData.GetNumMacros(layer) == 0) { return; - } + }*/ using (Packet packet = new Packet()) { // TODO: This should be improved to only send the macros to the layers that require the macro diff --git a/GK6X/Program.cs b/GK6X/Program.cs index e4d9625..5dbd4ef 100644 --- a/GK6X/Program.cs +++ b/GK6X/Program.cs @@ -44,6 +44,7 @@ static void Main(string[] args) { Log("Connected to device '" + device.State.ModelName + "' model:" + device.State.ModelId + " fw:" + device.State.FirmwareVersion); + WebGUI.UpdateDeviceList(); string file = GetUserDataFile(device); if (!string.IsNullOrEmpty(file)) @@ -69,6 +70,7 @@ static void Main(string[] args) KeyboardDeviceManager.Disconnected += (KeyboardDevice device) => { Log("Disconnected from device '" + device.State.ModelName + "'"); + WebGUI.UpdateDeviceList(); }; KeyboardDeviceManager.StartListener(); @@ -136,6 +138,9 @@ static void Main(string[] args) Log("Bad input. Expected folder name."); } break; + case "gui": + WebGUI.Run(); + break; case "map": case "unmap": { diff --git a/GK6X/UserDataFile.cs b/GK6X/UserDataFile.cs index 609355a..595a932 100644 --- a/GK6X/UserDataFile.cs +++ b/GK6X/UserDataFile.cs @@ -43,6 +43,7 @@ public uint GetKey(KeyboardState.Key key) public class Macro { public string Name; + public string Guid;// Used only for .cmf macros public int Id; public MacroRepeatType RepeatType; public byte RepeatCount; @@ -70,6 +71,163 @@ public class Action public byte KeyCode;// DriverValueMouseButton or short version of DriverValue public DriverValueModifer Modifier; public ushort Delay; + + /// + /// Used for the web gui string name mappings + /// + public string ValueStr; + } + + /// + /// Key names as defined in the "Macros" tab (this is seemingly not in the data files?) + /// + Dictionary keyNames = new Dictionary() + { + }; + + public bool LoadFile(string path) + { + if (File.Exists(path)) + { + byte[] buffer = CMFile.Load(path); + if (buffer != null) + { + string str = Encoding.UTF8.GetString(buffer); + string[] lines = str.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + string currentGroup = null; + Action currentAction = null; + for (int i = 0; i < lines.Length; i++) + { + string line = lines[i].Trim(); + if (line.StartsWith("[")) + { + line = line.Replace("[", string.Empty).Replace("]", string.Empty).ToLower(); + switch (line.ToLower()) + { + case "general": + case "script": + currentGroup = line; + break; + } + } + else if (currentGroup != null) + { + string key = null; + string value = null; + int keyIndex = line.IndexOf('='); + if (keyIndex <= 0) + { + keyIndex = line.IndexOf(' '); + } + if (keyIndex > 0) + { + key = line.Substring(0, keyIndex).Trim().ToLower(); + value = line.Substring(keyIndex + 1).Trim(); + } + else + { + key = line.ToLower(); + } + switch (currentGroup) + { + case "general": + { + switch (key) + { + case "name": + { + Name = value; + } + break; + case "scriptid": + { + Guid = value; + } + break; + case "repeats": + { + byte.TryParse(value, out RepeatCount); + } + break; + case "stopmode": + { + byte stopmode; + if (byte.TryParse(value, out stopmode)) + { + RepeatType = (MacroRepeatType)stopmode; + } + } + break; + } + } + break; + case "script": + { + switch (key) + { + case "keydown": + case "keyup": + { + value = value.Trim('\"'); + MacroKeyNames.Item macroKeyName; + if (MacroKeyNames.Names.TryGetValue(value, out macroKeyName)) + { + currentAction = null; + currentAction = new Action(); + currentAction.ValueStr = value; + if (KeyValues.IsKeyModifier(macroKeyName.DriverValue)) + { + currentAction.Modifier = KeyValues.GetKeyModifier(macroKeyName.DriverValue); + } + else + { + currentAction.KeyCode = KeyValues.GetShortDriverValue(macroKeyName.DriverValue); + } + currentAction.State = key.Contains("up") ? MacroKeyState.Up : MacroKeyState.Down; + currentAction.Type = MacroKeyType.Key; + Actions.Add(currentAction); + } + } + break; + case "leftdown": + case "leftup": + case "rightdown": + case "rightup": + currentAction = null; + currentAction = new Action(); + currentAction.State = key.Contains("up") ? MacroKeyState.Up : MacroKeyState.Down; + switch(key) + { + case "leftdown": + case "leftup": + currentAction.KeyCode = (byte)DriverValueMouseButton.LButton; + break; + case "rightdown": + case "rightup": + currentAction.KeyCode = (byte)DriverValueMouseButton.RButton; + break; + } + currentAction.Type = MacroKeyType.Mouse; + Actions.Add(currentAction); + break; + case "delay": + { + if (currentAction != null) + { + ushort.TryParse(value, out currentAction.Delay); + } + } + break; + } + } + break; + } + } + } + return true; + } + } + return false; } } @@ -148,13 +306,28 @@ public bool Load(KeyboardState keyboard) { try { - string fileName = Path.Combine(Program.DataBasePath, "lighting", Name + ".le"); - if (string.IsNullOrEmpty(Name) || !File.Exists(fileName)) + if (string.IsNullOrEmpty(Name)) { return false; } + string path = Path.Combine(Program.DataBasePath, "lighting", Name + ".le"); + if (!File.Exists(path)) + { + return false; + } + return Load(keyboard, File.ReadAllText(path)); + } + catch + { + return false; + } + } - Dictionary json = Json.Deserialize(File.ReadAllText(fileName)) as Dictionary; + public bool Load(KeyboardState keyboard, string str) + { + try + { + Dictionary json = Json.Deserialize(str) as Dictionary; if (json == null) { return false; @@ -187,7 +360,7 @@ public bool Load(KeyboardState keyboard) LoadDynamic(keyboard, json); break; } - + return true; } catch diff --git a/GK6X/WebGUI.cs b/GK6X/WebGUI.cs new file mode 100644 index 0000000..949441c --- /dev/null +++ b/GK6X/WebGUI.cs @@ -0,0 +1,907 @@ +using MiniJSON; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; + +namespace GK6X +{ + class WebGUI + { + public const int Port = 6464; + static WebServer server = new WebServer(Port); + + public static void Run() + { + string url = "http://localhost:" + Port; + if (!server.IsRunning) + { + server.Start(); + Program.Log("Started web GUI server at " + url); + } + Process.Start("chrome", "--incognito " + url); + } + + public static void UpdateDeviceList() + { + server.UpdateDeviceList(); + } + + class WebServer + { + private Thread thread; + private HttpListener listener; + private string dataPath; + private string userDataPath; + + public int Port { get; set; } + public bool IsRunning + { + get { return listener != null; } + } + + Dictionary sessions = new Dictionary(); + DateTime lastSessionCleanup; + TimeSpan sessionCleanupDelay = TimeSpan.FromSeconds(30); + TimeSpan sessionPingTimeout = TimeSpan.FromSeconds(10); + + class Session + { + public string Token; + public DateTime LastAccess; + public Queue> MessageQueue = new Queue>(); + + public void Enqueue(string functionName, string data) + { + lock (MessageQueue) + { + Dictionary message = new Dictionary(); + message["funcName"] = functionName; + message["data"] = data; + MessageQueue.Enqueue(message); + } + } + + public void UpdateDeviceList() + { + List modelInfos = new List(); + foreach (KeyboardDevice device in KeyboardDeviceManager.GetConnectedDevices()) + { + Dictionary modelInfo = new Dictionary(); + modelInfo["ModelID"] = device.State.ModelId; + modelInfos.Add(modelInfo); + } + Enqueue("onDeviceListChanged", Json.Serialize(modelInfos)); + } + } + + public WebServer(int port) + { + Port = port; + } + + public void UpdateDeviceList() + { + if (!IsRunning) + { + return; + } + lock (sessions) + { + foreach (Session session in sessions.Values) + { + session.UpdateDeviceList(); + } + } + } + + private void LazyCreateAccount(int accountId) + { + string accountDir = Path.Combine(userDataPath, "Account", accountId.ToString()); + if (!Directory.Exists(accountDir)) + { + Directory.CreateDirectory(accountDir); + + string devicesDir = Path.Combine(accountDir, "Devices"); + string leDir = Path.Combine(accountDir, "LE"); + string macroDir = Path.Combine(accountDir, "Macro"); + + Directory.CreateDirectory(devicesDir); + Directory.CreateDirectory(leDir); + Directory.CreateDirectory(macroDir); + + foreach (string file in Directory.GetFiles(Path.Combine(dataPath, "res", "data", "macro"), "*.cms")) + { + File.Copy(file, Path.Combine(macroDir, Path.GetFileName(file)), true); + } + File.Copy(Path.Combine(dataPath, "res", "data", "macro", "macrolist_en.json"), Path.Combine(macroDir, "macrolist.json"), true); + + foreach (string file in Directory.GetFiles(Path.Combine(dataPath, "res", "data", "le"), "*.le")) + { + File.Copy(file, Path.Combine(leDir, Path.GetFileName(file)), true); + } + File.Copy(Path.Combine(dataPath, "res", "data", "le", "lelist_en.json"), Path.Combine(leDir, "lelist.json"), true); + + foreach (string dir in Directory.GetDirectories(Path.Combine(dataPath, "device"))) + { + string profilePath = Path.Combine(dir, "data", "profile.json"); + if (File.Exists(profilePath)) + { + string targetDir = Path.Combine(devicesDir, new DirectoryInfo(dir).Name); + Directory.CreateDirectory(targetDir); + + foreach (string file in Directory.GetFiles(Path.Combine(dir, "data"), "*.json")) + { + try + { + string guid = Guid.NewGuid().ToString().ToUpper(); + Dictionary json = Json.Deserialize(File.ReadAllText(file)) as Dictionary; + json["GUID"] = guid; + string str = Json.Serialize(json); + File.WriteAllBytes(Path.Combine(targetDir, guid + ".cmf"), CMFile.Encrypt(Encoding.UTF8.GetBytes(str), CMFileType.Profile)); + } + catch + { + } + } + } + } + + // We need to add these model ids as otherwise it fails to pick up models correctly + Dictionary defaultConfig = new Dictionary(); + { + Dictionary userInit = new Dictionary(); + userInit["LE"] = true; + userInit["Macro"] = true; + defaultConfig["UserInit"] = userInit; + } + { + Dictionary modelInit = new Dictionary(); + foreach (string dir in Directory.GetDirectories(Path.Combine(dataPath, "device"))) + { + string profilePath = Path.Combine(dir, "data", "profile.json"); + if (File.Exists(profilePath)) + { + Dictionary modelInfo = new Dictionary(); + modelInfo["Macro"] = true; + modelInfo["LE"] = true; + modelInfo["Mode"] = 1; + modelInit[new DirectoryInfo(dir).Name] = modelInfo; + } + } + defaultConfig["ModelInit"] = modelInit; + } + File.WriteAllText(Path.Combine(accountDir, "Config.json"), Json.Serialize(defaultConfig)); + } + } + + public void Start() + { + dataPath = Program.BasePath; + string rootDir = Path.Combine(dataPath, "GK6XPlus Driver"); + if (Directory.Exists(rootDir)) + { + dataPath = rootDir; + } + string engineDir = Path.Combine(dataPath, "CMSEngine"); + if (Directory.Exists(engineDir)) + { + dataPath = engineDir; + } + string driverDir = Path.Combine(dataPath, "driver"); + if (Directory.Exists(driverDir)) + { + dataPath = driverDir; + } + + if (dataPath == Program.BasePath) + { + Program.Log("Couldn't find data path"); + return; + } + else if (!Directory.Exists(dataPath)) + { + Program.Log("Couldn't find data path '" + dataPath + "'"); + return; + } + userDataPath = Path.Combine(dataPath, "UserData"); + if (!Directory.Exists(userDataPath)) + { + Directory.CreateDirectory(userDataPath); + } + + Stop(); + + thread = new Thread(delegate() + { + listener = new HttpListener(); + listener.Prefixes.Add("http://localhost:" + Port + "/");// localhost only (as we don't have enough sanitization here...) + listener.Start(); + while (listener != null) + { + try + { + HttpListenerContext context = listener.GetContext(); + Process(context); + } + catch + { + } + + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + } + + public void Stop() + { + if (listener != null) + { + try + { + listener.Stop(); + } + catch + { + } + listener = null; + } + + if (thread != null) + { + try + { + thread.Abort(); + } + catch + { + } + thread = null; + } + } + + private void Process(HttpListenerContext context) + { + if (DateTime.Now - sessionCleanupDelay > lastSessionCleanup) + { + lastSessionCleanup = DateTime.Now; + lock (sessions) + { + foreach (KeyValuePair session in new Dictionary(sessions)) + { + if (DateTime.Now - sessionPingTimeout > session.Value.LastAccess) + { + sessions.Remove(session.Key); + } + } + } + } + + try + { + string url = context.Request.Url.OriginalString; + + byte[] responseBuffer = null; + string response = string.Empty; + string contentType = "text/html"; + + // This is for requests which don't need a response (used mostly for our crappy index.html error handler...) + const string successResponse = "OK!"; + + if (context.Request.Url.AbsolutePath == "/" || context.Request.Url.AbsolutePath.ToLower() == "/index.html") + { + string indexFile = Path.Combine(dataPath, "index.html"); + if (File.Exists(indexFile)) + { + string injectedJs = File.ReadAllText(Path.Combine(Program.DataBasePath, "WebGUI.js")); + injectedJs = injectedJs.Replace("UNIQUE_TOKEN_GOES_HERE", Guid.NewGuid().ToString()); + + response = File.ReadAllText(indexFile); + response = response.Insert(response.IndexOf("" + injectedJs + ""); + } + } + else if (context.Request.Url.AbsolutePath.StartsWith("/cms_")) + { + string postData = new StreamReader(context.Request.InputStream).ReadToEnd(); + Dictionary json = Json.Deserialize(postData) as Dictionary; + string token = json["token"].ToString(); + Session session; + if (!sessions.TryGetValue(token, out session)) + { + session = new Session(); + session.Token = token; + sessions[session.Token] = session; + } + session.LastAccess = DateTime.Now; + switch (json["requestType"].ToString()) + { + case "ping": + { + lock (session.MessageQueue) + { + List messages = new List(); + while (session.MessageQueue.Count > 0) + { + messages.Add(session.MessageQueue.Dequeue()); + } + response = Json.Serialize(messages); + } + } + break; + case "callFunc": + { + Dictionary request = Json.Deserialize((string)json["request"]) as Dictionary; + switch (request["funcname"].ToString()) + { + case "GetDeviceList": + { + session.UpdateDeviceList(); + response = successResponse; + } + break; + case "ChangeMode": + { + long modelId = (long)Convert.ChangeType(request["ModelID"], typeof(long)); + int modeIndex = (int)Convert.ChangeType(request["ModeIndex"], typeof(int)); + foreach (KeyboardDevice device in KeyboardDeviceManager.GetConnectedDevices()) + { + if (device.State.ModelId == modelId) + { + device.SetLayer((KeyboardLayer)modeIndex); + } + } + response = successResponse; + } + break; + case "GetProfileList": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + long modelId = (long)Convert.ChangeType(request["ModelID"], typeof(long)); + LazyCreateAccount(accountId); + string modelDir = Path.Combine(userDataPath, "Account", accountId.ToString(), "Devices", modelId.ToString()); + if (Directory.Exists(modelDir)) + { + List objs = new List(); + Dictionary modelsByIndex = new Dictionary(); + foreach (string file in Directory.GetFiles(modelDir, "*.cmf")) + { + object obj = Json.Deserialize(Encoding.UTF8.GetString(CMFile.Load(file))); + Dictionary profile = obj as Dictionary; + modelsByIndex[(int)Convert.ChangeType(profile["ModeIndex"], typeof(int))] = obj; + } + foreach (KeyValuePair obj in modelsByIndex.OrderBy(x => x.Key)) + { + objs.Add(obj.Value); + } + response = Json.Serialize(objs); + } + } + break; + case "ReadProfile": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + long modelId = (long)Convert.ChangeType(request["ModelID"], typeof(long)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Devices", modelId.ToString(), guid + ".cmf"); + if (File.Exists(file)) + { + response = Encoding.UTF8.GetString(CMFile.Load(file)); + } + } + break; + case "WriteProfile": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + long modelId = (long)Convert.ChangeType(request["ModelID"], typeof(long)); + string guid = (string)request["GUID"]; + string data = (string)request["Data"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Devices", modelId.ToString(), guid + ".cmf"); + if (File.Exists(file)) + { + File.WriteAllBytes(file, CMFile.Encrypt(Encoding.UTF8.GetBytes(data), CMFileType.Profile)); + response = successResponse; + } + } + break; + case "DeleteProfile": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + long modelId = (long)Convert.ChangeType(request["ModelID"], typeof(long)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Devices", modelId.ToString(), guid + ".cmf"); + if (File.Exists(file)) + { + try + { + File.Delete(file); + response = successResponse; + } + catch + { + } + } + } + break; + case "ApplyConfig": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + long modelId = (long)Convert.ChangeType(request["ModelID"], typeof(long)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Devices", modelId.ToString(), guid + ".cmf"); + if (File.Exists(file)) + { + string config = Encoding.UTF8.GetString(CMFile.Load(file)); + Dictionary data = Json.Deserialize(config) as Dictionary; + int modelIndex = (int)Convert.ChangeType(data["ModeIndex"], typeof(int)); + KeyboardLayer layer = (KeyboardLayer)modelIndex; + + foreach (KeyboardDevice device in KeyboardDeviceManager.GetConnectedDevices()) + { + if (device.State.ModelId == modelId) + { + Dictionary macrosById = new Dictionary(); + UserDataFile macrosUserDataFile = new UserDataFile(); + + ////////////////////////////////////////// + // Keys + ////////////////////////////////////////// + for (int i = 0; i < 2; i++) + { + string setStr = i == 0 ? "KeySet" : "FnKeySet"; + if (data.ContainsKey(setStr)) + { + uint[] driverValues = new uint[device.State.MaxLogicCode]; + for (int j = 0; j < driverValues.Length; j++) + { + driverValues[j] = KeyValues.UnusedKeyValue; + } + List keys = data[setStr] as List; + foreach (object keyObj in keys) + { + Dictionary key = keyObj as Dictionary; + int keyIndex = (int)Convert.ChangeType(key["Index"], typeof(int)); + uint driverValue = KeyValues.UnusedKeyValue; + string driverValueStr = (string)key["DriverValue"]; + if (driverValueStr.StartsWith("0x")) + { + if (uint.TryParse(driverValueStr.Substring(2), NumberStyles.HexNumber, null, out driverValue)) + { + if (KeyValues.GetKeyType(driverValue) == DriverValueType.Macro && key.ContainsKey("Task")) + { + Dictionary task = key["Task"] as Dictionary; + if (task != null && (string)task["Type"] == "Macro") + { + Dictionary taskData = task["Data"] as Dictionary; + string macroGuid = (string)taskData["GUID"]; + string macroFile = Path.Combine(userDataPath, "Account", accountId.ToString(), "Macro", macroGuid + ".cms"); + if (File.Exists(macroFile)) + { + UserDataFile.Macro macro = new UserDataFile.Macro(null); + macro.LoadFile(macroFile); + macro.RepeatCount = (byte)Convert.ChangeType(taskData["Repeats"], typeof(byte)); + macro.RepeatType = (MacroRepeatType)(byte)Convert.ChangeType(taskData["StopMode"], typeof(byte)); + macro.Id = KeyValues.GetKeyData2(driverValue); + macrosById[macro.Id] = macro; + macrosUserDataFile.Macros[macroGuid] = macro; + } + } + } + } + else + { + driverValue = KeyValues.UnusedKeyValue; + } + } + if (keyIndex >= 0) + { + Debug.WriteLine(device.State.KeysByLogicCode[keyIndex].KeyName + " = " + (DriverValue)driverValue); + driverValues[keyIndex] = driverValue; + } + } + device.SetKeys(layer, driverValues, i == 1); + } + } + + ////////////////////////////////////////// + // Lighting + ////////////////////////////////////////// + UserDataFile userDataFile = new UserDataFile(); + Dictionary effects = new Dictionary(); + string[] leHeaders = { "ModeLE", "DriverLE" }; + foreach (string leHeader in leHeaders) + { + if (data.ContainsKey(leHeader)) + { + List leEntries = data[leHeader] as List; + if (leEntries == null) + { + // There's only one ModeLE + leEntries = new List(); + leEntries.Add(data[leHeader]); + } + foreach (object entry in leEntries) + { + Dictionary modeLE = entry as Dictionary; + string leGuid = (string)modeLE["GUID"]; + string filePath = Path.Combine(userDataPath, "Account", accountId.ToString(), "LE", leGuid + ".le"); + if (!effects.ContainsKey(leGuid) && File.Exists(filePath)) + { + UserDataFile.LightingEffect le = new UserDataFile.LightingEffect(userDataFile, null); + le.Load(device.State, Encoding.UTF8.GetString(CMFile.Load(filePath))); + le.Layers.Add(layer); + userDataFile.LightingEffects[leGuid] = le; + effects[leGuid] = le; + } + } + } + } + device.SetLighting(layer, userDataFile); + + ////////////////////////////////////////// + // Macros + ////////////////////////////////////////// + device.SetMacros(layer, macrosUserDataFile); + + device.SetLayer(layer); + } + } + + session.Enqueue("onApplyResult", "{\"result\":1}"); + response = successResponse; + } + else + { + session.Enqueue("onApplyResult", "{\"result\":0}"); + } + } + break; + case "ReadFile": + { + int type = (int)Convert.ChangeType(request["Type"], typeof(int)); + string path = (string)request["Path"]; + string basePath = null; + switch (type) + { + case 0:// Data + basePath = dataPath; + break; + case 1:// User data + basePath = userDataPath; + if (path.StartsWith("Account/")) + { + LazyCreateAccount(int.Parse(path.Split('/')[1])); + } + break; + default: + Program.Log("Unhandled ReadFile type " + type); + break; + } + if (!string.IsNullOrEmpty(basePath)) + { + string fullPath = Path.Combine(basePath, path); + //Program.Log("ReadFile: " + fullPath); + if (File.Exists(fullPath) && IsFileInDirectoryOrSubDirectory(fullPath, dataPath)) + { + response = File.ReadAllText(fullPath); + } + } + } + break; + case "WriteFile": + { + int type = (int)Convert.ChangeType(request["Type"], typeof(int)); + string path = (string)request["Path"]; + string data = (string)request["Data"]; + string basePath = null; + switch (type) + { + case 0:// Data + basePath = dataPath; + break; + case 1:// User data + basePath = userDataPath; + if (path.StartsWith("Account/")) + { + LazyCreateAccount(int.Parse(path.Split('/')[1])); + } + break; + default: + Program.Log("Unhandled WriteFile type " + type); + break; + } + if (!string.IsNullOrEmpty(basePath)) + { + string fullPath = Path.Combine(basePath, path); + Program.Log("WriteFile: " + fullPath); + if (IsFileInDirectoryOrSubDirectory(fullPath, dataPath)) + { + string dir = Path.GetDirectoryName(fullPath); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + File.WriteAllText(fullPath, data); + response = successResponse; + } + } + } + break; + case "ReadLE": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + string guid = (string)request["GUID"]; + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "LE", guid + ".le"); + LazyCreateAccount(accountId); + if (File.Exists(file)) + { + response = Encoding.UTF8.GetString(CMFile.Load(file)); + } + } + break; + case "WriteLE": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + string guid = (string)request["GUID"]; + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "LE", guid + ".le"); + string data = (string)request["Data"]; + LazyCreateAccount(accountId); + File.WriteAllBytes(file, CMFile.Encrypt(Encoding.UTF8.GetBytes(data), CMFileType.Light)); + response = successResponse; + } + break; + case "DeleteLE": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "LE", guid + ".le"); + if (File.Exists(file)) + { + File.Delete(file); + response = successResponse; + } + } + break; + case "ReadMacrofile": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Macro", guid + ".cms"); + if (File.Exists(file)) + { + UserDataFile.Macro macro = new UserDataFile.Macro(null); + if (macro.LoadFile(file)) + { + Dictionary macroJson = new Dictionary(); + macroJson["MacroName"] = macro.Name; + macroJson["GUID"] = macro.Guid; + List taskList = new List(); + macroJson["TaskList"] = taskList; + foreach (UserDataFile.Macro.Action action in macro.Actions) + { + Dictionary actionJson = new Dictionary(); + if (action.Type == MacroKeyType.Key) + { + switch (action.State) + { + case MacroKeyState.Down: + actionJson["taskName"] = "KeyDown"; + break; + case MacroKeyState.Up: + actionJson["taskName"] = "KeyUp"; + break; + } + } + else + { + string button = null; + switch ((DriverValueMouseButton)action.KeyCode) + { + case DriverValueMouseButton.LButton: + button = "Left"; + break; + case DriverValueMouseButton.RButton: + button = "Right"; + break; + } + if (!string.IsNullOrEmpty(button)) + { + switch (action.State) + { + case MacroKeyState.Down: + button += "Down"; + break; + case MacroKeyState.Up: + button += "Up"; + break; + } + } + actionJson["taskName"] = button; + } + actionJson["taskValue"] = action.ValueStr != null ? "\"" + action.ValueStr + "\"" : string.Empty; + taskList.Add(actionJson); + if (action.Delay > 0) + { + Dictionary delayJson = new Dictionary(); + delayJson["taskName"] = "Delay"; + delayJson["taskValue"] = action.Delay; + taskList.Add(delayJson); + } + } + response = Json.Serialize(macroJson); + } + } + } + break; + case "WriteMacrofile": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Macro", guid + ".cms"); + string data = (string)request["Data"]; + + Dictionary macroJson = Json.Deserialize(data) as Dictionary; + + StringBuilder macroStr = new StringBuilder(); + macroStr.AppendLine("[General]"); + macroStr.AppendLine("Name=" + macroJson["MacroName"]); + macroStr.AppendLine("ScriptID=" + guid); + macroStr.AppendLine("Repeats=1"); + macroStr.AppendLine("StopMode=1"); + macroStr.AppendLine(); + macroStr.AppendLine("[Script]"); + + List taskListJson = macroJson["TaskList"] as List; + foreach (object taskObj in taskListJson) + { + Dictionary task = taskObj as Dictionary; + if (!string.IsNullOrEmpty(task["taskValue"].ToString())) + { + macroStr.AppendLine((string)task["taskName"] + " " + task["taskValue"].ToString()); + } + else + { + macroStr.AppendLine((string)task["taskName"]); + } + } + + if (!string.IsNullOrEmpty(guid)) + { + File.WriteAllBytes(file, CMFile.Encrypt(Encoding.UTF8.GetBytes(macroStr.ToString()), CMFileType.Macro)); + response = successResponse; + } + } + break; + case "DeleteMacrofile": + { + int accountId = (int)Convert.ChangeType(request["AccoutID"], typeof(int)); + string guid = (string)request["GUID"]; + LazyCreateAccount(accountId); + string file = Path.Combine(userDataPath, "Account", accountId.ToString(), "Macro", guid + ".cms"); + if (File.Exists(file)) + { + File.Delete(file); + response = successResponse; + } + } + break; + } + } + break; + } + } + else + { + // This needs some sanitization... + string file = Path.Combine(dataPath, context.Request.Url.AbsolutePath.Substring(1)); + if (File.Exists(file) && IsFileInDirectoryOrSubDirectory(file, dataPath)) + { + string extension = Path.GetExtension(file).ToLower(); + switch (extension) + { + case ".js": + response = File.ReadAllText(file); + contentType = "application/javascript"; + break; + case ".png": + responseBuffer = File.ReadAllBytes(file); + contentType = "image/png"; + break; + case ".jpg": + case ".jpeg": + responseBuffer = File.ReadAllBytes(file); + contentType = "image/jpeg"; + break; + case ".json": + responseBuffer = File.ReadAllBytes(file); + contentType = "application/json"; + break; + case ".cmsl": + case ".html": + responseBuffer = File.ReadAllBytes(file); + contentType = "text/html"; + break; + case ".css": + responseBuffer = File.ReadAllBytes(file); + contentType = "text/css"; + break; + default: + Program.Log("Unhandled file type " + extension + " " + context.Request.Url.AbsolutePath); + break; + } + } + } + + if (responseBuffer == null && response != null) + { + responseBuffer = Encoding.UTF8.GetBytes(response.ToString()); + } + + context.Response.ContentType = contentType; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.ContentLength64 = responseBuffer.Length; + context.Response.OutputStream.Write(responseBuffer, 0, responseBuffer.Length); + context.Response.OutputStream.Flush(); + context.Response.StatusCode = (int)HttpStatusCode.OK; + } + catch + { + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + } + + context.Response.OutputStream.Close(); + } + + bool IsFileInDirectoryOrSubDirectory(string filePath, string directory) + { + return IsSameOrSubDirectory(directory, Path.GetDirectoryName(filePath)); + } + + bool IsSameOrSubDirectory(string basePath, string path) + { + string subDirectory; + return IsSameOrSubDirectory(basePath, path, out subDirectory); + } + + bool IsSameOrSubDirectory(string basePath, string path, out string subDirectory) + { + DirectoryInfo di = new DirectoryInfo(Path.GetFullPath(path).TrimEnd('\\', '/')); + DirectoryInfo diBase = new DirectoryInfo(Path.GetFullPath(basePath).TrimEnd('\\', '/')); + + subDirectory = null; + while (di != null) + { + if (di.FullName.Equals(diBase.FullName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else + { + if (string.IsNullOrEmpty(subDirectory)) + { + subDirectory = di.Name; + } + else + { + subDirectory = Path.Combine(di.Name, subDirectory); + } + di = di.Parent; + } + } + return false; + } + } + } +} diff --git a/ScreenshotWeb.png b/ScreenshotWeb.png new file mode 100644 index 0000000..4cabb37 Binary files /dev/null and b/ScreenshotWeb.png differ