/// <summary> /// 转换 NCM 文件为 Flac/MP3 文件 /// </summary> /// <param name="filePath">NCM 文件路径</param> /// <returns>转换状态</returns> public NCMConverterEnum ProcessFile(string filePath) { using (var fs = File.Open(filePath, FileMode.Open)) { // 校验是否是网易云加密的 NCM 文件 if (NCMExtenstion.ReadInt32(fs) != 0x4e455443) { return(NCMConverterEnum.Invalid); } if (NCMExtenstion.ReadInt32(fs) != 0x4d414446) { return(NCMConverterEnum.Invalid); } // 读取密钥 NCMExtenstion.Seek(fs, 2); var keyBytes = NCMExtenstion.ReadBytes(fs, NCMExtenstion.ReadInt32(fs)); for (int i = 0; i < keyBytes.Length; i++) { keyBytes[i] ^= 0x64; } // 减去 "neteasecloudmusic" 字符串之后的数据即为密钥数据 var deKeyDataBytes = NCMExtenstion.GetBytesByOffset(NCMExtenstion.DecryptAes128Ecb(Encoding.UTF8.GetBytes(NCMConveterConsts.AesCoreKeyString), keyBytes), 17); var modifyDataBytes = NCMExtenstion.ReadBytes(fs, NCMExtenstion.ReadInt32(fs)); for (int i = 0; i < modifyDataBytes.Length; i++) { modifyDataBytes[i] ^= 0x63; } var decryptBase64 = SystemConvert.FromBase64String(Encoding.UTF8.GetString(NCMExtenstion.GetBytesByOffset(modifyDataBytes, 22))); var decryptModifyDataBytes = NCMExtenstion.DecryptAes128Ecb(Encoding.UTF8.GetBytes(NCMConveterConsts.AesModifyKeyBytes), decryptBase64); // 获取 NCM 文件的歌曲文件信息 Json var musicJson = JObject.Parse(Encoding.UTF8.GetString(NCMExtenstion.GetBytesByOffset(decryptModifyDataBytes, 6))); var fileExtension = musicJson.SelectToken("$.format").Value <string>(); // 跳过 CRC 校验 NCMExtenstion.Seek(fs, 9); // 跳过专辑图像数据 var imageBytes = new byte[NCMExtenstion.ReadInt32(fs)]; if (imageBytes.Length != 0) { fs.Read(imageBytes, 0, imageBytes.Length); } var box = NCMExtenstion.BuildKeyBox(deKeyDataBytes); var n = 0x8000; using (var outputFile = File.Create(Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}.{fileExtension}"))) { while (true) { var tb = new byte[n]; var result = fs.Read(tb, 0, n); if (result <= 0) { break; } for (int i = 0; i < n; i++) { var j = (byte)((i + 1) & 0xff); tb[i] ^= box[box[j] + box[(box[j] + j) & 0xff] & 0xff]; } outputFile.Write(tb, 0, n); } outputFile.Flush(); } fs.Close(); return(NCMConverterEnum.Success); } }
object IParser.Parse(XElement e) { string name; object id; byte[] content; Entity entity; bool templateByDefault; EnumTreeType treeType; ParseObject(e, out id, out name); var fileNameAttribute = e.Attribute("fileName"); if (fileNameAttribute == null) { throw new ArgumentException(Message.Get("Xml.NoAttribute", "fileName", e), "e"); } var typeNameAttribute = e.Attribute("typeName"); if (typeNameAttribute == null) { throw new ArgumentException(Message.Get("Xml.NoAttribute", "typeName", e), "e"); } var typeCodeAttribute = e.Attribute("typeCode"); if (typeCodeAttribute == null) { throw new ArgumentException(Message.Get("Xml.NoAttribute", "typeCode", e), "e"); } var entityIDAttribute = e.Attribute("entityID"); if (entityIDAttribute == null) { throw new ArgumentException(Message.Get("Xml.NoAttribute", "entityID", e), "e"); } entity = Storage.Select <Entity>(entityIDAttribute.Value); var templateByDefaultAttribute = e.Attribute("templateByDefault"); if (templateByDefaultAttribute == null) { throw new ArgumentException(Message.Get("Xml.NoAttribute", "entityID", e), "e"); } templateByDefault = templateByDefaultAttribute.Value == "1" ? true : false; var treeTypeIDAttribute = e.Attribute("templateByDefault"); if (treeTypeIDAttribute == null) { throw new ArgumentException(Message.Get("Xml.NoAttribute", "entityID", e), "e"); } treeType = GetTreeTypeID(e.Attribute("treeTypeID").Value); List <TemplateField> fields = new List <TemplateField>(); var fieldsElement = e.Element("fields"); if (fieldsElement != null) { var fieldElements = fieldsElement.Elements("field"); if (fieldElements.Count() == 0) { throw new ArgumentException(Message.Get("Xml.NoElements", "field", e), "e"); } templateFieldParser = new TemplateFieldParser(entity); fields = fieldElements.Select(o => templateFieldParser.Parse(o)).ToList(); } var contentNode = e.Element("content"); if (contentNode == null) { throw new ArgumentException(Message.Get("Xml.NoElement", "content", e), "content"); } try { content = Convert.FromBase64String(contentNode.Value); } catch { #warning Использую ArithmeticException. throw new ArithmeticException("Не удалось преобразовать тело документа к массиву байт."); } var sheetAttribute = e.Attribute("sheet"); Dictionary <string, string> parameters = null; if (sheetAttribute != null) { parameters = new System.Collections.Generic.Dictionary <string, string>(); parameters["sheet"] = sheetAttribute.Value; } return(new Template(id, name, entity, fileNameAttribute.Value, typeNameAttribute.Value, typeCodeAttribute.Value, templateByDefault, treeType, content, fields, parameters)); }