You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

654 regels
19 KiB

  1. local bit32 = (bit32 or require("bit"))
  2. -- 一个缓存文件
  3. local CacheFile = defClass("CacheFile"
  4. , defVar("origin" , VT_String)
  5. , defVar("name" , VT_String)
  6. , defVar("md5" , VT_String)
  7. , defVar("originSize" , VT_Double)
  8. , defVar("compileSize" , VT_Double)
  9. , defVar("encryptSize" , VT_Double)
  10. , defVar("compressedSize" , VT_Double)
  11. , defVar("modifyTime" , VT_Double)
  12. , defVar("compressedFile" , VT_String)
  13. , defVar("encryptFile" , VT_String)
  14. );
  15. -- 一组缓存文件
  16. local CacheFileList = defClass("CacheFileList"
  17. , defVar("list" , VT_Map(VT_String , CacheFile))
  18. );
  19. -- 保存所有缓存的文件
  20. gCacheFiles = CacheFileList:new();
  21. -- 所有配置文件的缓存
  22. gConfigDescCacheFiles = nil
  23. -- 保存所有的xml文件对应的desc解析文件
  24. gConfigXmlToDesc = {}
  25. local function getNextPowerOfTwo(value)
  26. local i = 1;
  27. while i < value do
  28. i = i * 2;
  29. end
  30. return i;
  31. end
  32. local function isPow2(size)
  33. return getNextPowerOfTwo(size) == size;
  34. end
  35. -- 载入文件数据
  36. function loadfileData(filename)
  37. local fileData;
  38. -- 读取整个文件数据
  39. local file = io.open(filename , "rb");
  40. fileData = file:read("*a");
  41. file:close();
  42. return fileData;
  43. end
  44. --/* These describe the color_type field in png_info. */
  45. --/* color type masks */
  46. PNG_COLOR_MASK_PALETTE = 1
  47. PNG_COLOR_MASK_COLOR = 2
  48. PNG_COLOR_MASK_ALPHA = 4
  49. --/* color types. Note that not all combinations are legal */
  50. PNG_COLOR_TYPE_GRAY = 0
  51. PNG_COLOR_TYPE_PALETTE = (PNG_COLOR_MASK_COLOR + PNG_COLOR_MASK_PALETTE)
  52. PNG_COLOR_TYPE_RGB = (PNG_COLOR_MASK_COLOR)
  53. PNG_COLOR_TYPE_RGB_ALPHA = (PNG_COLOR_MASK_COLOR + PNG_COLOR_MASK_ALPHA)
  54. PNG_COLOR_TYPE_GRAY_ALPHA =(PNG_COLOR_MASK_ALPHA)
  55. -- 编译png文件成pvr文件,并返回pvr文件名
  56. local function _compilePngToPvr(sourceFile)
  57. local pathName , baseName , ext = string.splitFilename(sourceFile);
  58. -- 只打包这三个目录
  59. if not (string.startsWith(pathName , "res/effect/")
  60. or string.startsWith(pathName , "res/animation/")
  61. or string.startsWith(pathName , "res/scene/")) then
  62. return sourceFile;
  63. end
  64. local image = cc.Image:analysisPngFile(sourceFile);
  65. -- PVR转换规则,长宽相等,大小是2次幂。
  66. if image.image_width ~= image.image_height then
  67. return sourceFile;
  68. end
  69. -- 不是2次幂
  70. if not isPow2(image.image_width) or not isPow2(image.image_height) then
  71. return sourceFile;
  72. end
  73. -- 最小8*8
  74. if image.image_width < 8 or image.image_height < 8 then
  75. return sourceFile;
  76. end
  77. local pvrFormat;
  78. print("image" , table.tostring(image));
  79. -- 根据纹理格式来决定pvr格式
  80. if image.color_type == PNG_COLOR_TYPE_RGB then
  81. pvrFormat = "PVRTC1_4_RGB";
  82. elseif image.color_type == PNG_COLOR_TYPE_RGB_ALPHA then
  83. pvrFormat = "PVRTC1_4";
  84. elseif image.color_type == PNG_COLOR_TYPE_GRAY_ALPHA then
  85. pvrFormat = "PVRTC1_2";
  86. elseif image.color_type == PNG_COLOR_TYPE_GRAY then
  87. pvrFormat = "PVRTC1_2_RGB";
  88. elseif image.color_type == PNG_COLOR_TYPE_PALETTE then
  89. if image.expand_color_type == PNG_COLOR_TYPE_RGB_ALPHA then
  90. print("调色板带透明");
  91. pvrFormat = "PVRTC1_4";
  92. else
  93. print("调色板不带透明");
  94. pvrFormat = "PVRTC1_2_RGB";
  95. end
  96. else
  97. return sourceFile;
  98. end
  99. local pvrFile = "cache/" .. baseName .. "." .. pvrFormat .. ".pvr";
  100. local exFlags = "";
  101. -- 场景光效人物,要用mipmap
  102. --exFlags = exFlags .. " -m";
  103. local cmdLine = string.format('PVRTexTool -i "%s" -o "%s" -f "%s"%s' , sourceFile , pvrFile , pvrFormat , exFlags);
  104. local out = io.popen(cmdLine , "r");
  105. print("正在转换成pvr:" .. cmdLine);
  106. print(out:read("*a"));
  107. out:close();
  108. local atts = lfs.attributes(pvrFile , "modification");
  109. if not atts then
  110. error("转换pvr失败" .. sourceFile);
  111. return sourceFile;
  112. end
  113. return pvrFile;
  114. end
  115. -- 编译png文件成pvr文件,并返回pvr文件数据
  116. local function compilePngToPvr(sourceFile)
  117. local fileName = _compilePngToPvr(sourceFile);
  118. return loadfileData(fileName);
  119. end
  120. -- 编译png文件成etc文件,并返回etc文件名
  121. local function _compilePngToEtc(sourceFile)
  122. local pathName , baseName , ext = string.splitFilename(sourceFile);
  123. -- 只打包这三个目录
  124. if not (string.startsWith(pathName , "res/effect/")
  125. or string.startsWith(pathName , "res/animation/")
  126. or string.startsWith(pathName , "res/scene/")) then
  127. return sourceFile;
  128. end
  129. local image = cc.Image:analysisPngFile(sourceFile);
  130. -- 不是2次幂
  131. if not isPow2(image.image_width) or not isPow2(image.image_height) then
  132. return sourceFile;
  133. end
  134. -- 最小8*8
  135. if image.image_width < 8 or image.image_height < 8 then
  136. return sourceFile;
  137. end
  138. local etcFormat;
  139. -- 根据纹理格式来决定etc格式
  140. if image.expand_color_type == PNG_COLOR_TYPE_RGB or image.expand_color_type == PNG_COLOR_TYPE_GRAY then
  141. etcFormat = "ETC1";
  142. else
  143. return sourceFile;
  144. end
  145. local etcFile = "cache/" .. baseName .. "." .. etcFormat .. ".pvr";
  146. local exFlags = "";
  147. -- 场景光效人物,要用mipmap
  148. --exFlags = exFlags .. " -m";
  149. local cmdLine = string.format('PVRTexTool -i "%s" -o "%s" -f "%s"%s' , sourceFile , etcFile , etcFormat , exFlags);
  150. local out = io.popen(cmdLine , "r");
  151. print("正在转换成etc:" .. cmdLine);
  152. print(out:read("*a"));
  153. out:close();
  154. local atts = lfs.attributes(etcFile , "modification");
  155. if not atts then
  156. error("转换etc失败" .. sourceFile);
  157. return sourceFile;
  158. end
  159. return etcFile;
  160. end
  161. -- 编译png文件成etc文件,并返回etc文件数据
  162. local function compilePngToEtc(sourceFile)
  163. local fileName = _compilePngToEtc(sourceFile);
  164. return loadfileData(fileName);
  165. end
  166. -- 编译png文件成etc文件,并返回etc文件名
  167. local function _compilePngToAtc(sourceFile)
  168. local pathName , baseName , ext = string.splitFilename(sourceFile);
  169. if string.startsWith(pathName , "res/scene/scene_maoxian_002") then
  170. print("res/scene/scene_maoxian_002不能压缩纹理,文件名" .. sourceFile);
  171. return sourceFile;
  172. end
  173. -- 只打包这三个目录
  174. if not (string.startsWith(pathName , "res/effect/")
  175. or string.startsWith(pathName , "res/animation/")
  176. or string.startsWith(pathName , "res/ui/")
  177. or string.startsWith(pathName , "res/scene/"))then
  178. print("指定目录之外的纹理不需要压缩,文件名" .. sourceFile);
  179. return sourceFile;
  180. end
  181. local image = cc.Image:analysisPngFile(sourceFile);
  182. -- 最小8*8
  183. if image.image_width < 8 or image.image_height < 8 then
  184. print("长宽小于8不能压缩纹理,文件名" .. sourceFile);
  185. return sourceFile;
  186. end
  187. local etcFormat;
  188. -- 根据纹理格式来决定atc格式
  189. if image.color_type == PNG_COLOR_TYPE_RGB or image.expand_color_type == PNG_COLOR_TYPE_RGB then
  190. etcFormat = "RGB";
  191. elseif image.color_type == PNG_COLOR_TYPE_RGB_ALPHA or image.expand_color_type == PNG_COLOR_TYPE_RGB_ALPHA then
  192. etcFormat = "RGBA"
  193. else
  194. print("Texture color type is", image.color_type)
  195. print("Texture expand color type is", image.expand_color_type)
  196. print("纹理格式不是RGB或者是RGBA格式,不能转换成压缩纹理,文件名" .. sourceFile);
  197. return sourceFile;
  198. end
  199. --
  200. local atcFile = "cache/" .. baseName .. "." .. etcFormat .. ".atc";
  201. local cmdLine = string.format('atc_texture "%s" "%s"' , sourceFile , atcFile);
  202. local out = io.popen(cmdLine , "r");
  203. print("正在转换成atc:" .. cmdLine);
  204. print(out:read("*a"));
  205. out:close();
  206. local atts = lfs.attributes(atcFile , "modification");
  207. if not atts then
  208. error("转换atc失败" .. sourceFile);
  209. return sourceFile;
  210. end
  211. return atcFile;
  212. end
  213. -- 编译png文件成atc文件,并返回atc文件数据
  214. local function compilePngToAtc(sourceFile)
  215. local fileName = _compilePngToAtc(sourceFile);
  216. return loadfileData(fileName);
  217. end
  218. -- 编译lua文件,并返回编译后的数据
  219. function compileLua52(filename)
  220. -- 编译lua
  221. print("正在编译Lua52:" , filename);
  222. local cmdLine = string.format('luac.exe -o "luac_tmp.bin" "%s"' , cc.FileUtils:getInstance():fullPathForFilename(filename));
  223. local out = io.popen(cmdLine , "r");
  224. print("正在运行:" .. cmdLine);
  225. print(out:read("*a"));
  226. local r , what , code = out:close();
  227. if code ~= 0 then
  228. error("编译脚本出错:" .. filename);
  229. end
  230. return loadfileData(cc.FileUtils:getInstance():fullPathForFilename("luac_tmp.bin"));
  231. end
  232. -- 编译lua文件,并返回编译后的数据
  233. function compileLuajit(filename)
  234. -- 编译lua
  235. print("正在编译Luajit:" , filename);
  236. local cmdLine = string.format('luajit -b -g "%s" "luac_tmp.bin"' , cc.FileUtils:getInstance():fullPathForFilename(filename));
  237. local out = io.popen(cmdLine , "r");
  238. print("正在运行:" .. cmdLine);
  239. print(out:read("*a"));
  240. local r , what , code = out:close();
  241. if code ~= 0 then
  242. error("编译脚本出错:" .. filename);
  243. end
  244. return loadfileData(cc.FileUtils:getInstance():fullPathForFilename("luac_tmp.bin"));
  245. end
  246. function compileLua(filename)
  247. if hasCmdArg("-lua") then
  248. return compileLua52(filename);
  249. else
  250. return compileLuajit(filename);
  251. end
  252. end
  253. local ConfigLoaded = false;
  254. function loadAllXML()
  255. if ConfigLoaded then return; end
  256. ConfigLoaded = true;
  257. local s = app.config.Setting;
  258. local r = app.config.RomSetting;
  259. app.config = require("LoadAllConfigs")
  260. app.config.Setting = s;
  261. app.config.RomSetting = r;
  262. for i, v in pairs(app.config) do
  263. if type(v) == "function" then
  264. app.config[i] = v()
  265. end
  266. end
  267. end
  268. -- 获取对应版本的lua后缀名
  269. local function getLuaExtName(baseName)
  270. local saveBaseName
  271. if hasCmdArg("-lua") then
  272. saveBaseName = baseName .. ".lua52";
  273. else
  274. saveBaseName = baseName .. ".luajit";
  275. end
  276. return saveBaseName
  277. end
  278. -- 判断一个策划配置的xml文件依赖的desc文件是否改变
  279. local function isConfigXmlDescChanged(xmlFileName)
  280. local descFileName = gConfigXmlToDesc[xmlFileName]
  281. if not descFileName then
  282. print("配置文件:" .. xmlFileName .. "统计不到对应的desc缓存文件,xml文件需要重新打包")
  283. return true
  284. end
  285. -- 读取文件
  286. local atts = lfs.attributes(descFileName);
  287. if not atts then
  288. error("找不到文件" .. descFileName);
  289. return true;
  290. end
  291. -- 拆分desc文件名
  292. local pathName, baseName, ext = string.splitFilename(descFileName);
  293. local saveBaseName = getLuaExtName(baseName)
  294. local cacheFile = gConfigDescCacheFiles.list[saveBaseName];
  295. if cacheFile then
  296. if cacheFile.origin == descFileName and cacheFile.modifyTime == atts.modification and cacheFile.originSize == atts.size then
  297. -- 文件没有改变则不需要重新生成
  298. return false
  299. end
  300. end
  301. print("配置文件:" .. xmlFileName .. "对应的desc文件:" .. descFileName .. "改变了,xml文件需要重新打包")
  302. return true
  303. end
  304. local function optimiseRewardConfig(RewardConfig)
  305. local deleted = {}
  306. for i , v in pairs(RewardConfig) do
  307. --print("删掉空奖励:" , table.tostring(v));
  308. if not v.epFull
  309. and not v.apFull
  310. and #v.gold == 1 and v.gold[1] == ""
  311. and #v.ticket == 1 and v.ticket[1] == ""
  312. and #v.redGem == 1 and v.redGem[1] == ""
  313. and #v.goods == 1 and #v.goods[1] == 1 and v.goods[1][1] == ""
  314. and #v.treasure == 0
  315. and #v.awardHero == 0
  316. and #v.awardHeroSaga == 0
  317. and #v.awardRune == 0
  318. and #v.awardRuneChip == 0
  319. and #v.awardHeroSoul == 0
  320. and (not v.awardPrivilege or v.awardPrivilege == 0)
  321. then
  322. table.insert(deleted , i);
  323. print("删掉空奖励:" , i);
  324. end
  325. end
  326. print("总共删掉空奖励:" , #deleted);
  327. for i , v in ipairs(deleted) do
  328. RewardConfig[v] = nil;
  329. end
  330. end
  331. local function optimiseHeroAwardConfig(AwardConfig)
  332. local deleted = {}
  333. for i , v in pairs(AwardConfig) do
  334. if #v.awardValue == 0 or (#v.awardDungeon == 1 and #v.awardDungeon[1] == 0) then
  335. table.insert(deleted , i);
  336. print("删掉如何获得的空配置:" , i);
  337. end
  338. end
  339. print("总共删掉如何获得空配置:" , #deleted);
  340. for i , v in ipairs(deleted) do
  341. AwardConfig[v] = nil;
  342. end
  343. end
  344. local function copyFile(dstFile , srcFile)
  345. local src = io.open(srcFile , "rb");
  346. local dst = io.open(dstFile , "wb");
  347. dst:write(src:read("*a"));
  348. dst:close();
  349. src:close();
  350. end
  351. -- 编译xml文件,并返回编译后的数据
  352. function compileXml(sourceFile)
  353. loadAllXML();
  354. local luaData;
  355. for i , v in pairs(app.config) do
  356. if i ~= "Setting" and i ~= "RomSetting" then
  357. if type(v.LuaFile) == "string" then
  358. local filename = v.XmlFile;
  359. if "dataconfig/" .. filename == sourceFile then
  360. local luaFile = "cache/" .. filename .. ".lua";
  361. if filename == "pvpRewardConfig.xml"
  362. or filename == "riftRewardConfig.xml"
  363. or filename == "castleRewardConfig.xml"
  364. or filename == "vipRewardConfig.xml"
  365. or filename == "commonRewardConfig.xml"
  366. or filename == "appRewardConfig.xml"
  367. or filename == "rankRewardConfig.xml"
  368. or filename == "pveRewardConfig.xml"
  369. or filename == "achieveRewardConfig.xml" then
  370. optimiseRewardConfig(v);
  371. end
  372. if filename == "awardHeroSoulConfig.xml"
  373. or filename == "awardStarUpFoodConfig.xml" then
  374. optimiseHeroAwardConfig(v);
  375. end
  376. copyFile(luaFile , v.LuaFile);
  377. luaData = compileLua(luaFile);
  378. break;
  379. end
  380. else
  381. local desc = v:getConfigDesc();
  382. local filename = desc.XMLFile;
  383. if "dataconfig/" .. filename == sourceFile then
  384. local luaFile = "cache/" .. filename .. ".lua";
  385. if filename == "pvpRewardConfig.xml"
  386. or filename == "riftRewardConfig.xml"
  387. or filename == "castleRewardConfig.xml"
  388. or filename == "vipRewardConfig.xml"
  389. or filename == "commonRewardConfig.xml"
  390. or filename == "appRewardConfig.xml"
  391. or filename == "rankRewardConfig.xml"
  392. or filename == "pveRewardConfig.xml"
  393. or filename == "achieveRewardConfig.xml" then
  394. optimiseRewardConfig(v);
  395. end
  396. if filename == "awardHeroSoulConfig.xml"
  397. or filename == "awardStarUpFoodConfig.xml" then
  398. optimiseHeroAwardConfig(v);
  399. end
  400. saveLuaXMLConfig(v , desc, luaFile);
  401. luaData = compileLua(luaFile);
  402. break;
  403. end
  404. end
  405. end
  406. end
  407. if luaData ~= nil then
  408. return lzma.compress(luaData);
  409. end
  410. end
  411. local keepFiles = {};
  412. -- 编译文件
  413. function compileFile(sourceFile, targetPath)
  414. local pathName , baseName , ext = string.splitFilename(sourceFile);
  415. local lowerExt = string.lower(ext);
  416. if isIgnoreFile(pathName , baseName , ext) then
  417. return;
  418. end
  419. local atts = lfs.attributes(sourceFile);
  420. if not atts then
  421. error("找不到文件" .. sourceFile);
  422. return;
  423. end
  424. local keepFile = keepFiles[pathName];
  425. if not keepFile then
  426. keepFile = {};
  427. keepFiles[pathName] = keepFile;
  428. local f , r = io.open(pathName .. "/keep.txt" , "rb");
  429. print("open keep" , f , r);
  430. if f then
  431. while true do
  432. local fileName = f:read("*l");
  433. print("read keep" , fileName);
  434. if fileName then
  435. fileName = string.trim(fileName);
  436. keepFile[fileName] = true;
  437. print("set keep " , fileName);
  438. else
  439. break;
  440. end
  441. end
  442. f:close();
  443. end
  444. end
  445. local saveBaseName = baseName;
  446. if not keepFile[baseName] then
  447. -- 处理png
  448. if lowerExt == "png" then
  449. -- preload要用png格式,有些机子不支持etc
  450. if pathName ~= "preload" then
  451. -- 是否支持PVR格式
  452. if hasCmdArg("-pvr") then
  453. saveBaseName = saveBaseName .. ".pvr";
  454. end
  455. -- 是否支持PVR格式
  456. if hasCmdArg("-etc") then
  457. saveBaseName = saveBaseName .. ".etc";
  458. end
  459. -- 是否支持PVR格式
  460. if hasCmdArg("-atc") then
  461. saveBaseName = saveBaseName .. ".atc";
  462. end
  463. end
  464. end
  465. -- lua文件
  466. if (isLuaFileExt(lowerExt) or (lowerExt == "xml" and pathName == "dataconfig")) then
  467. saveBaseName = getLuaExtName(saveBaseName)
  468. end
  469. else
  470. print("file keeped : " , baseName);
  471. saveBaseName = baseName .. ".keep";
  472. end
  473. local cacheFile = gCacheFiles.list[saveBaseName];
  474. if cacheFile then
  475. if cacheFile.origin == sourceFile and cacheFile.modifyTime == atts.modification and cacheFile.originSize == atts.size then
  476. -- 如果是dataconfig里面的xml则需要对比下desc文件是否改变了
  477. if lowerExt == "xml" and pathName == "dataconfig" then
  478. if isConfigXmlDescChanged(baseName) == false then
  479. print("配置文件没有改变,不需要重新打包", baseName)
  480. return cacheFile
  481. end
  482. elseif baseName ~= "Setting.lua" then
  483. return cacheFile;
  484. end
  485. end
  486. end
  487. print("编译文件" .. sourceFile)
  488. local fileData;
  489. if not keepFile[baseName] then
  490. print("skip keeped : " , baseName);
  491. -- 处理png
  492. if lowerExt == "png" then
  493. -- preload要用png格式,有些机子不支持etc
  494. if pathName ~= "preload" then
  495. -- 是否支持PVR格式
  496. if hasCmdArg("-pvr") then
  497. fileData = compilePngToPvr(sourceFile);
  498. end
  499. if hasCmdArg("-etc") then
  500. fileData = compilePngToEtc(sourceFile);
  501. end
  502. if hasCmdArg("-atc") then
  503. fileData = compilePngToAtc(sourceFile);
  504. end
  505. else
  506. print("preload的文件不用编译png" , sourceFile);
  507. end
  508. -- 编译配置文件
  509. elseif lowerExt == "xml" then
  510. fileData = compileXml(sourceFile);
  511. -- 编译lua文件
  512. elseif isLuaFileExt(lowerExt) then
  513. fileData = compileLua(sourceFile);
  514. end
  515. end
  516. -- 普通文件加载
  517. if fileData == nil then
  518. fileData = loadfileData(sourceFile);
  519. end
  520. -- 计算文件信息
  521. local fileInfo = CacheFile:new();
  522. fileInfo.origin = sourceFile;
  523. fileInfo.name = baseName;
  524. fileInfo.md5 = md5.sumhexa(fileData);
  525. fileInfo.compileSize = #fileData;
  526. fileInfo.originSize = atts.size;
  527. -- 保存修改时间
  528. fileInfo.modifyTime = atts.modification;
  529. -- 加密文件
  530. local encryptData = FilePackage.encrypt(fileData)
  531. fileInfo.encryptSize = string.len(encryptData);
  532. -- 加密完毕后,计算出加密后大小
  533. fileInfo.encryptFile = targetPath .. "/cache/" .. saveBaseName .. ".encrypt";
  534. saveFile(encryptData , fileInfo.encryptFile);
  535. -- 压缩文件
  536. local compressedData = FilePackage.compress(encryptData)
  537. fileInfo.compressedSize = string.len(compressedData);
  538. -- 压缩完毕后,计算出压缩后大小
  539. fileInfo.compressedFile = targetPath .."/cache/" .. saveBaseName .. ".compressed";
  540. saveFile(compressedData , fileInfo.compressedFile);
  541. gCacheFiles.list[saveBaseName] = fileInfo;
  542. --print("编译文件完毕" .. sourceFile);
  543. return fileInfo;
  544. end
  545. -- 保存文件列表
  546. function saveCacheFile(filename)
  547. local stream = NetStream.new();
  548. gCacheFiles:write(stream);
  549. print("保存缓存数据:" , #gCacheFiles.list)
  550. local cacheData = NetStream.getWriteData(stream);
  551. saveFile(cacheData , filename);
  552. NetStream.delete(stream);
  553. end
  554. -- 载入文件列表
  555. function loadCacheFile(filename)
  556. gCacheFiles = CacheFileList:new();
  557. local cacheData = loadFile(filename);
  558. if cacheData then
  559. local stream = NetStream.new(cacheData);
  560. gCacheFiles = CacheFileList:read(stream);
  561. print("载入缓存数据:" , #gCacheFiles.list)
  562. NetStream.delete(stream);
  563. end
  564. end
  565. -- 载入配置描述列表
  566. function loadConfigDescCacheFile(configDescFileName)
  567. gConfigDescCacheFiles = CacheFileList:new();
  568. local cacheData = loadFile(configDescFileName);
  569. if cacheData then
  570. local stream = NetStream.new(cacheData);
  571. gConfigDescCacheFiles = CacheFileList:read(stream);
  572. NetStream.delete(stream);
  573. end
  574. end