Commit b9b7f455c07f300c2b08c8a978a4da182a0e817c

Authored by chenyueqi
2 parents 51ab9871 fe41ea5d

Merge branch 'cn/develop' into cn/publish/preview

@@ -18,6 +18,9 @@ START_RESET_TIME = START_RESET_TIME_BASE - TIME_ZONE * 3600 @@ -18,6 +18,9 @@ START_RESET_TIME = START_RESET_TIME_BASE - TIME_ZONE * 3600
18 18
19 STRUCT_VERSION = 3 -- 数据结构版本 19 STRUCT_VERSION = 3 -- 数据结构版本
20 20
  21 +IOS_SID = 4 -- 判断是不是ios设备
  22 +UO_SID = 6 -- 判断是不是联运渠道
  23 +
21 MAX_ROLE_NUM = 1000000 24 MAX_ROLE_NUM = 1000000
22 -- 属性枚举 25 -- 属性枚举
23 AttsEnum = { 26 AttsEnum = {
src/actions/GmAction.lua
@@ -669,6 +669,7 @@ end @@ -669,6 +669,7 @@ end
669 669
670 function _M.cz(role, pms) 670 function _M.cz(role, pms)
671 local id = tonum(pms.pm1) 671 local id = tonum(pms.pm1)
  672 + local sid = tonum(pms.pm2) or 0
672 local csvData = csvdb["shop_rechargeCsv"][id] 673 local csvData = csvdb["shop_rechargeCsv"][id]
673 if not csvData then 674 if not csvData then
674 return "充值id错误, 查看shop_recharge.csv" 675 return "充值id错误, 查看shop_recharge.csv"
@@ -678,6 +679,7 @@ function _M.cz(role, pms) @@ -678,6 +679,7 @@ function _M.cz(role, pms)
678 transactionId = "GM", 679 transactionId = "GM",
679 order = "GM", 680 order = "GM",
680 pay_time = skynet.timex(), 681 pay_time = skynet.timex(),
  682 + sid = sid == 1 and IOS_SID or 0,
681 }) 683 })
682 role:mylog("gm_action", {desc = "recharge", int1 = id, key1 = pms.sender}) 684 role:mylog("gm_action", {desc = "recharge", int1 = id, key1 = pms.sender})
683 return "指令成功" 685 return "指令成功"
src/actions/RoleAction.lua
@@ -1020,8 +1020,6 @@ function _M.achiveRpc(agent, data) @@ -1020,8 +1020,6 @@ function _M.achiveRpc(agent, data)
1020 return true 1020 return true
1021 end 1021 end
1022 1022
1023 -  
1024 -  
1025 function _M.chatRpc(agent, data) 1023 function _M.chatRpc(agent, data)
1026 local role = agent.role 1024 local role = agent.role
1027 local roleId = role:getProperty("id") 1025 local roleId = role:getProperty("id")
@@ -1036,10 +1034,21 @@ function _M.chatRpc(agent, data) @@ -1036,10 +1034,21 @@ function _M.chatRpc(agent, data)
1036 -- 判断禁言 1034 -- 判断禁言
1037 local result = nil 1035 local result = nil
1038 1036
1039 - local SERV = string_format(".chated%d", math.random(1, 5))  
1040 - local legal, mod = skynet.call(SERV, "lua", "check", content)  
1041 - if not legal then  
1042 - content = mod or "" 1037 + local sdkResult
  1038 + if role:getProperty("sid") == UO_SID then
  1039 + sdkResult = role:uoChatSDK(content)
  1040 + else
  1041 + sdkResult = role:biliChatSDK(content)
  1042 + end
  1043 +
  1044 + if sdkResult then
  1045 + content = sdkResult
  1046 + else
  1047 + local SERV = string_format(".chated%d", math.random(1, 5))
  1048 + local legal, mod = skynet.call(SERV, "lua", "check", content)
  1049 + if not legal then
  1050 + content = mod or ""
  1051 + end
1043 end 1052 end
1044 1053
1045 if content == "" then 1054 if content == "" then
src/models/Order.lua
@@ -12,6 +12,7 @@ Order.schema = { @@ -12,6 +12,7 @@ Order.schema = {
12 createTime = {"number", skynet.timex()}, -- 订单创建时间 12 createTime = {"number", skynet.timex()}, -- 订单创建时间
13 finishTime = {"number", 0}, -- 服务端验证完成时间 13 finishTime = {"number", 0}, -- 服务端验证完成时间
14 status = {"string", "create"}, 14 status = {"string", "create"},
  15 + sid = {"number",0}, -- 创建单号的设备的sid
15 } 16 }
16 17
17 Order.fields = { 18 Order.fields = {
@@ -21,6 +22,7 @@ Order.fields = { @@ -21,6 +22,7 @@ Order.fields = {
21 createTime = true, 22 createTime = true,
22 finishTime = true, 23 finishTime = true,
23 status = true, 24 status = true,
  25 + sid = true,
24 } 26 }
25 27
26 return Order 28 return Order
27 \ No newline at end of file 29 \ No newline at end of file
src/models/Role.lua
@@ -59,8 +59,9 @@ Role.schema = { @@ -59,8 +59,9 @@ Role.schema = {
59 ignoreMt = {"number", 0}, -- 忽略维护拦截 59 ignoreMt = {"number", 0}, -- 忽略维护拦截
60 sversion = {"number", STRUCT_VERSION or 0}, -- 重整数据版本 60 sversion = {"number", STRUCT_VERSION or 0}, -- 重整数据版本
61 timeReset = {"table", {}}, --重置轮回记录 61 timeReset = {"table", {}}, --重置轮回记录
62 - diamond = {"number", 0},  
63 - reDiamond = {"number", 0}, 62 + diamond = {"number", 0}, -- 免费钻
  63 + reDiamond = {"number", 0}, -- android充值钻
  64 + reDiamondIos = {"number", 0}, -- ios充值钻
64 setting = {"table", {}}, --设置 65 setting = {"table", {}}, --设置
65 codeStr = {"string", ""}, --已经领过的礼包码 66 codeStr = {"string", ""}, --已经领过的礼包码
66 -- roleInfo 67 -- roleInfo
src/models/RolePlugin.lua
  1 +local httpc = require("http.httpc")
  2 +local md5 = require "md5"
  3 +local cjson = require "shared.json"
1 4
2 local serverId = tonumber(skynet.getenv("servId")) 5 local serverId = tonumber(skynet.getenv("servId"))
3 local RolePlugin = {} 6 local RolePlugin = {}
@@ -442,7 +445,8 @@ function RolePlugin.bind(Role) @@ -442,7 +445,8 @@ function RolePlugin.bind(Role)
442 end 445 end
443 446
444 function Role:getAllDiamond() 447 function Role:getAllDiamond()
445 - return self:getProperty("diamond") + self:getProperty("reDiamond") 448 + local diamond = self:getProperty("sid") == IOS_SID and self:getProperty("reDiamondIos") or self:getProperty("reDiamond")
  449 + return self:getProperty("diamond") + diamond
446 end 450 end
447 451
448 function Role:gainDiamond(params) 452 function Role:gainDiamond(params)
@@ -452,10 +456,14 @@ function RolePlugin.bind(Role) @@ -452,10 +456,14 @@ function RolePlugin.bind(Role)
452 return false 456 return false
453 end 457 end
454 local origind = self:getProperty("diamond") 458 local origind = self:getProperty("diamond")
455 - local originr = self:getProperty("reDiamond") 459 + local originr = self:getProperty("sid") == IOS_SID and self:getProperty("reDiamondIos") or self:getProperty("reDiamond")
456 local origin = origind + originr 460 local origin = origind + originr
457 if params.isRecharge then 461 if params.isRecharge then
458 - self:incrProperty("reDiamond", count) 462 + if params.sid == IOS_SID then
  463 + self:incrProperty("reDiamondIos", count)
  464 + else
  465 + self:incrProperty("reDiamond", count)
  466 + end
459 else 467 else
460 self:incrProperty("diamond", count) 468 self:incrProperty("diamond", count)
461 end 469 end
@@ -486,8 +494,9 @@ function RolePlugin.bind(Role) @@ -486,8 +494,9 @@ function RolePlugin.bind(Role)
486 if count <= 0 then 494 if count <= 0 then
487 return false 495 return false
488 end 496 end
  497 + local isIos = self:getProperty("sid") == IOS_SID
489 local origind = self:getProperty("diamond") 498 local origind = self:getProperty("diamond")
490 - local originr = self:getProperty("reDiamond") 499 + local originr = isIos and self:getProperty("reDiamondIos") or self:getProperty("reDiamond")
491 local origin = origind + originr 500 local origin = origind + originr
492 501
493 if origin < 0 then 502 if origin < 0 then
@@ -497,9 +506,9 @@ function RolePlugin.bind(Role) @@ -497,9 +506,9 @@ function RolePlugin.bind(Role)
497 return false 506 return false
498 end 507 end
499 local last = count 508 local last = count
500 - local costFirst = {"diamond", "reDiamond"} 509 + local costFirst = isIos and {"diamond", "reDiamondIos"} or {"diamond", "reDiamond"}
501 if params.isRecharge then 510 if params.isRecharge then
502 - costFirst = {"reDiamond", "diamond"} 511 + costFirst = isIos and {"reDiamondIos", "diamond"} or {"reDiamond", "diamond"}
503 end 512 end
504 last = math.max(last - self:getProperty(costFirst[1]), 0) 513 last = math.max(last - self:getProperty(costFirst[1]), 0)
505 if last < count then 514 if last < count then
@@ -1750,6 +1759,7 @@ function RolePlugin.bind(Role) @@ -1750,6 +1759,7 @@ function RolePlugin.bind(Role)
1750 rechargeId = rechargeId, 1759 rechargeId = rechargeId,
1751 createTime = skynet.timex(), 1760 createTime = skynet.timex(),
1752 transactionId = transactionId, 1761 transactionId = transactionId,
  1762 + sid = self:getProperty("sid"),
1753 }) 1763 })
1754 order:create() 1764 order:create()
1755 -- 正在进行中的订单 缓存 1765 -- 正在进行中的订单 缓存
@@ -1780,6 +1790,7 @@ function RolePlugin.bind(Role) @@ -1780,6 +1790,7 @@ function RolePlugin.bind(Role)
1780 1790
1781 local rechargeId = orderObject:getProperty("rechargeId") 1791 local rechargeId = orderObject:getProperty("rechargeId")
1782 local dataSet = csvdb["shop_rechargeCsv"][rechargeId] 1792 local dataSet = csvdb["shop_rechargeCsv"][rechargeId]
  1793 + local sid = orderObject:getProperty("sid")
1783 1794
1784 if orderObject:getProperty("finishTime") > 0 then 1795 if orderObject:getProperty("finishTime") > 0 then
1785 skynet.error(string.format("[recharge] is a finish order cpOrder: %s, platformOrder : %s, hadPlatformOrder: %s, id: %s, overTime : %s", 1796 skynet.error(string.format("[recharge] is a finish order cpOrder: %s, platformOrder : %s, hadPlatformOrder: %s, id: %s, overTime : %s",
@@ -1817,7 +1828,7 @@ function RolePlugin.bind(Role) @@ -1817,7 +1828,7 @@ function RolePlugin.bind(Role)
1817 }) 1828 })
1818 end 1829 end
1819 1830
1820 - return true, rechargeId 1831 + return true, rechargeId, sid
1821 end 1832 end
1822 1833
1823 -- 充值 -- 1834 -- 充值 --
@@ -1835,7 +1846,7 @@ function RolePlugin.bind(Role) @@ -1835,7 +1846,7 @@ function RolePlugin.bind(Role)
1835 local roleId = self:getProperty("id") 1846 local roleId = self:getProperty("id")
1836 local partnerOrderStr = params.order 1847 local partnerOrderStr = params.order
1837 1848
1838 - local status, back = self:updatePurchaseOrder(partnerOrderStr, params.transactionId, "finsh") 1849 + local status, back, sid = self:updatePurchaseOrder(partnerOrderStr, params.transactionId, "finsh")
1839 if not status then 1850 if not status then
1840 if back == "finsh" then 1851 if back == "finsh" then
1841 -- 订单已经处理 1852 -- 订单已经处理
@@ -1857,6 +1868,7 @@ function RolePlugin.bind(Role) @@ -1857,6 +1868,7 @@ function RolePlugin.bind(Role)
1857 transactionId = params.transactionId, 1868 transactionId = params.transactionId,
1858 pay_time = params.pay_time, 1869 pay_time = params.pay_time,
1859 order = partnerOrderStr, 1870 order = partnerOrderStr,
  1871 + sid = sid,
1860 }) 1872 })
1861 1873
1862 if not status then 1874 if not status then
@@ -1893,7 +1905,7 @@ function RolePlugin.bind(Role) @@ -1893,7 +1905,7 @@ function RolePlugin.bind(Role)
1893 rechargeF[id] = 1 1905 rechargeF[id] = 1
1894 self:updateProperty({field = "rechargeF", value = rechargeF}) 1906 self:updateProperty({field = "rechargeF", value = rechargeF})
1895 end 1907 end
1896 - self:gainDiamond({count = diamondCount, isRecharge = true, log = {desc = "recharge", int1 = id}}) 1908 + self:gainDiamond({count = diamondCount, isRecharge = true, sid = params.sid, log = {desc = "recharge", int1 = id}})
1897 elseif rechargeData.shop == 2 then --通行证商店 1909 elseif rechargeData.shop == 2 then --通行证商店
1898 reward, _ = self:award(rechargeData.itemFirst, {isRecharge = true, log = {desc = "recharge", int1 = id}}) 1910 reward, _ = self:award(rechargeData.itemFirst, {isRecharge = true, log = {desc = "recharge", int1 = id}})
1899 self.storeData:onBuyCard(rechargeData.type, rechargeData.time, rechargeData.id, rechargeData.activity_id) 1911 self.storeData:onBuyCard(rechargeData.type, rechargeData.time, rechargeData.id, rechargeData.activity_id)
@@ -2146,6 +2158,107 @@ function RolePlugin.bind(Role) @@ -2146,6 +2158,107 @@ function RolePlugin.bind(Role)
2146 return hero:getProperty("faith") 2158 return hero:getProperty("faith")
2147 end 2159 end
2148 2160
  2161 + local function table_keys( t )
  2162 + local keys = {}
  2163 + for k, _ in pairs( t ) do
  2164 + keys[#keys + 1] = k
  2165 + end
  2166 + return keys
  2167 + end
  2168 +
  2169 + local function makeStr(params, key, urlsign)
  2170 + local keys = table_keys(params)
  2171 + table.sort(keys)
  2172 + local sign2Str, requestStr = "", ""
  2173 + for _, key in ipairs(keys) do
  2174 + sign2Str = sign2Str .. (urlsign and urlencode(params[key]) or params[key])
  2175 + requestStr = requestStr .. string.format("%s=%s&",key,urlencode(params[key]))
  2176 + end
  2177 + local sign = md5.sumhexa(sign2Str .. key):lower()
  2178 + requestStr = requestStr .. string.format("sign=%s",sign)
  2179 +
  2180 + return requestStr
  2181 + end
  2182 +
  2183 + function Role:biliChatSDK(content)
  2184 + if content == "" then return end
  2185 + local base64Content = base64.encode(content)
  2186 + local uids = self:getProperty("uid"):split2("_")
  2187 + local uid = uids[2] or self:getProperty("uid")
  2188 +
  2189 + local urls = {
  2190 + "http://pnew.biligame.net",
  2191 + "http://pserver.bilibiligame.net"
  2192 + }
  2193 + local secret = "8920e9dcf0cb4ebca87393ce48021ead"
  2194 +
  2195 + local headers = {
  2196 + ["User-Agent"] = 'Mozilla/5.0 GameServer',
  2197 + ["Content-Type"] = "application/x-www-form-urlencoded",
  2198 + }
  2199 +
  2200 + local send = {
  2201 + game_id = 4818,
  2202 + uid = uid,
  2203 + merchant_id = 1,
  2204 + server_id = 3957,
  2205 + version = "1",
  2206 + timestamp = math.floor(skynet.timex() * 1000),
  2207 + content = base64Content,
  2208 + }
  2209 +
  2210 + local params = makeStr(send, secret)
  2211 + httpc.timeout = 100
  2212 + local status, body = httpc.request("POST", urls[1], "/api/server/censor", {}, headers, params)
  2213 + if tonumber(status) ~= 200 then
  2214 + status, body = httpc.request("POST", urls[2], "/api/server/censor", {}, headers, params)
  2215 + end
  2216 + if tonumber(status) ~= 200 then
  2217 + return
  2218 + end
  2219 + local result = json.decode(body)
  2220 + if not result or result.code ~= 0 then
  2221 + return
  2222 + end
  2223 + return result.data.content
  2224 + end
  2225 +
  2226 + function Role:uoChatSDK(content)
  2227 + if content == "" then return end
  2228 + local base64Content = base64.encode(content)
  2229 + local uids = self:getProperty("uid"):split2("_")
  2230 + local uid = uids[2] or self:getProperty("uid")
  2231 +
  2232 + local urls = {
  2233 + "http://uosdk.biligame.com",
  2234 + }
  2235 + local secret = "4243b5fb44b64175a20a53dcfb1346eb"
  2236 +
  2237 + local headers = {
  2238 + ["User-Agent"] = 'Mozilla/5.0 CP-Game-Server',
  2239 + ["Content-Type"] = "application/x-www-form-urlencoded",
  2240 + }
  2241 +
  2242 + local send = {
  2243 + app_id = 4821,
  2244 + user_id = uid,
  2245 + timestamp = math.floor(skynet.timex() * 1000),
  2246 + content = base64Content,
  2247 + }
  2248 +
  2249 + local params = makeStr(send, secret, true)
  2250 + httpc.timeout = 100
  2251 + local status, body = httpc.request("POST", urls[1], "/api/server/censor", {}, headers, params)
  2252 + if tonumber(status) ~= 200 then
  2253 + return
  2254 + end
  2255 + local result = json.decode(body)
  2256 + if not result or result.code ~= 0 then
  2257 + return
  2258 + end
  2259 + return result.data.content
  2260 + end
  2261 +
2149 end 2262 end
2150 2263
2151 return RolePlugin 2264 return RolePlugin
2152 \ No newline at end of file 2265 \ No newline at end of file
src/shared/base64.lua 0 → 100644
@@ -0,0 +1,155 @@ @@ -0,0 +1,155 @@
  1 +local base64 = {}
  2 +local string = string
  3 +
  4 +base64.__code = {
  5 + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  6 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
  7 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
  8 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
  9 + };
  10 +base64.__decode = {}
  11 +for k,v in pairs(base64.__code) do
  12 + base64.__decode[string.byte(v,1)] = k - 1
  13 +end
  14 +
  15 +function base64.encode(text)
  16 + local len = string.len(text)
  17 + local left = len % 3
  18 + len = len - left
  19 + local res = {}
  20 + local index = 1
  21 + for i = 1, len, 3 do
  22 + local a = string.byte(text, i )
  23 + local b = string.byte(text, i + 1)
  24 + local c = string.byte(text, i + 2)
  25 + -- num = a<<16 + b<<8 + c
  26 + local num = a * 65536 + b * 256 + c
  27 + for j = 1, 4 do
  28 + --tmp = num >> ((4 -j) * 6)
  29 + local tmp = math.floor(num / (2 ^ ((4-j) * 6)))
  30 + --curPos = tmp&0x3f
  31 + local curPos = tmp % 64 + 1
  32 + res[index] = base64.__code[curPos]
  33 + index = index + 1
  34 + end
  35 + end
  36 +
  37 + if left == 1 then
  38 + base64.__left1(res, index, text, len)
  39 + elseif left == 2 then
  40 + base64.__left2(res, index, text, len)
  41 + end
  42 + return table.concat(res)
  43 +end
  44 +
  45 +function base64.__left2(res, index, text, len)
  46 + local num1 = string.byte(text, len + 1)
  47 + num1 = num1 * 1024 --lshift 10
  48 + local num2 = string.byte(text, len + 2)
  49 + num2 = num2 * 4 --lshift 2
  50 + local num = num1 + num2
  51 +
  52 + local tmp1 = math.floor(num / 4096) --rShift 12
  53 + local curPos = tmp1 % 64 + 1
  54 + res[index] = base64.__code[curPos]
  55 +
  56 + local tmp2 = math.floor(num / 64)
  57 + curPos = tmp2 % 64 + 1
  58 + res[index + 1] = base64.__code[curPos]
  59 +
  60 + curPos = num % 64 + 1
  61 + res[index + 2] = base64.__code[curPos]
  62 +
  63 + res[index + 3] = "="
  64 +end
  65 +
  66 +function base64.__left1(res, index,text, len)
  67 + local num = string.byte(text, len + 1)
  68 + num = num * 16
  69 +
  70 + tmp = math.floor(num / 64)
  71 + local curPos = tmp % 64 + 1
  72 + res[index ] = base64.__code[curPos]
  73 +
  74 + curPos = num % 64 + 1
  75 + res[index + 1] = base64.__code[curPos]
  76 +
  77 + res[index + 2] = "="
  78 + res[index + 3] = "="
  79 +end
  80 +
  81 +function base64.decode(text)
  82 + local len = string.len(text)
  83 + local left = 0
  84 + if string.sub(text, len - 1) == "==" then
  85 + left = 2
  86 + len = len - 4
  87 + elseif string.sub(text, len) == "=" then
  88 + left = 1
  89 + len = len - 4
  90 + end
  91 +
  92 + local res = {}
  93 + local index = 1
  94 + local decode = base64.__decode
  95 + for i =1, len, 4 do
  96 + local a = decode[string.byte(text,i )]
  97 + local b = decode[string.byte(text,i + 1)]
  98 + local c = decode[string.byte(text,i + 2)]
  99 + local d = decode[string.byte(text,i + 3)]
  100 +
  101 + --num = a<<18 + b<<12 + c<<6 + d
  102 + local num = a * 262144 + b * 4096 + c * 64 + d
  103 +
  104 + local e = string.char(num % 256)
  105 + num = math.floor(num / 256)
  106 + local f = string.char(num % 256)
  107 + num = math.floor(num / 256)
  108 + res[index ] = string.char(num % 256)
  109 + res[index + 1] = f
  110 + res[index + 2] = e
  111 + index = index + 3
  112 + end
  113 +
  114 + if left == 1 then
  115 + base64.__decodeLeft1(res, index, text, len)
  116 + elseif left == 2 then
  117 + base64.__decodeLeft2(res, index, text, len)
  118 + end
  119 + return table.concat(res)
  120 +end
  121 +
  122 +function base64.__decodeLeft1(res, index, text, len)
  123 + local decode = base64.__decode
  124 + local a = decode[string.byte(text, len + 1)]
  125 + local b = decode[string.byte(text, len + 2)]
  126 + local c = decode[string.byte(text, len + 3)]
  127 + local num = a * 4096 + b * 64 + c
  128 +
  129 + local num1 = math.floor(num / 1024) % 256
  130 + local num2 = math.floor(num / 4) % 256
  131 + res[index] = string.char(num1)
  132 + res[index + 1] = string.char(num2)
  133 +end
  134 +
  135 +function base64.__decodeLeft2(res, index, text, len)
  136 + local decode = base64.__decode
  137 + local a = decode[string.byte(text, len + 1)]
  138 + local b = decode[string.byte(text, len + 2)]
  139 + local num = a * 64 + b
  140 + num = math.floor(num / 16)
  141 + res[index] = string.char(num)
  142 +end
  143 +
  144 +function base64.test()
  145 + local data = "a\193\207="
  146 + local abc = base64.encode(data)
  147 + print(abc)
  148 +
  149 + def = base64.decode(abc)
  150 + if def == data then
  151 + print("yes")
  152 + end
  153 +end
  154 +
  155 +return base64
0 \ No newline at end of file 156 \ No newline at end of file
src/shared/init.lua
@@ -2,4 +2,5 @@ require(&quot;shared.functions&quot;) @@ -2,4 +2,5 @@ require(&quot;shared.functions&quot;)
2 require("shared.debug") 2 require("shared.debug")
3 3
4 json = require("shared.json") 4 json = require("shared.json")
5 -MsgPack = require "cmsgpack"  
6 \ No newline at end of file 5 \ No newline at end of file
  6 +MsgPack = require "cmsgpack"
  7 +base64 = require("shared.base64")
7 \ No newline at end of file 8 \ No newline at end of file