1 /*
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "resource_pack.h"
17 #include <algorithm>
18 #include <iomanip>
19 #include "file_entry.h"
20 #include "file_manager.h"
21 #include "header.h"
22 #include "resource_check.h"
23 #include "resource_merge.h"
24 #include "resource_table.h"
25 #include "compression_parser.h"
26 #include "binary_file_packer.h"
27 
28 namespace OHOS {
29 namespace Global {
30 namespace Restool {
31 using namespace std;
ResourcePack(const PackageParser &packageParser)32 ResourcePack::ResourcePack(const PackageParser &packageParser):packageParser_(packageParser)
33 {
34 }
35 
Package()36 uint32_t ResourcePack::Package()
37 {
38     if (!packageParser_.GetAppend().empty()) {
39         return PackAppend();
40     }
41 
42     if (packageParser_.GetCombine()) {
43         return PackCombine();
44     }
45     return PackNormal();
46 }
47 
InitCompression()48 uint32_t ResourcePack::InitCompression()
49 {
50     if (!packageParser_.GetCompressionPath().empty()) {
51         auto compressionMgr = CompressionParser::GetCompressionParser(packageParser_.GetCompressionPath());
52         compressionMgr->SetOutPath(packageParser_.GetOutput());
53         if (compressionMgr->Init() != RESTOOL_SUCCESS) {
54             return RESTOOL_ERROR;
55         }
56     }
57     return RESTOOL_SUCCESS;
58 }
59 
60 // below private founction
Init()61 uint32_t ResourcePack::Init()
62 {
63     InitHeaderCreater();
64     if (InitOutput() != RESTOOL_SUCCESS) {
65         return RESTOOL_ERROR;
66     }
67 
68     if (InitConfigJson() != RESTOOL_SUCCESS) {
69         return RESTOOL_ERROR;
70     }
71 
72     if (InitModule() != RESTOOL_SUCCESS) {
73         return RESTOOL_ERROR;
74     }
75     return RESTOOL_SUCCESS;
76 }
77 
InitModule()78 uint32_t ResourcePack::InitModule()
79 {
80     ResourceIdCluster hapType = ResourceIdCluster::RES_ID_APP;
81     string packageName = packageParser_.GetPackageName();
82     if (packageName == "ohos.global.systemres") {
83         hapType = ResourceIdCluster::RES_ID_SYS;
84     }
85 
86     moduleName_ = configJson_.GetModuleName();
87     vector<string> moduleNames = packageParser_.GetModuleNames();
88     IdWorker &idWorker = IdWorker::GetInstance();
89     int64_t startId = static_cast<int64_t>(packageParser_.GetStartId());
90     if (startId > 0) {
91         return idWorker.Init(hapType, startId);
92     }
93 
94     if (moduleNames.empty()) {
95         return idWorker.Init(hapType);
96     } else {
97         sort(moduleNames.begin(), moduleNames.end());
98         auto it = find_if(moduleNames.begin(), moduleNames.end(), [this](auto iter) {
99                 return moduleName_ == iter;
100             });
101         if (it == moduleNames.end()) {
102             string buffer(" ");
103             for_each(moduleNames.begin(), moduleNames.end(), [&buffer](const auto &iter) {
104                     buffer.append(" " + iter + " ");
105                 });
106             cerr << "Error: module name '" << moduleName_ << "' not in [" << buffer << "]" << endl;
107             return RESTOOL_ERROR;
108         }
109 
110         startId = ((it - moduleNames.begin()) + 1) * 0x01000000;
111         if (startId >= 0x07000000) {
112             startId = startId + 0x01000000;
113         }
114         return idWorker.Init(hapType, startId);
115     }
116     return RESTOOL_SUCCESS;
117 }
118 
InitHeaderCreater()119 void ResourcePack::InitHeaderCreater()
120 {
121     using namespace placeholders;
122     headerCreaters_.emplace(".txt", bind(&ResourcePack::GenerateTextHeader, this, _1));
123     headerCreaters_.emplace(".js", bind(&ResourcePack::GenerateJsHeader, this, _1));
124     headerCreaters_.emplace(".h", bind(&ResourcePack::GenerateCplusHeader, this, _1));
125 }
126 
InitOutput() const127 uint32_t ResourcePack::InitOutput() const
128 {
129     bool forceWrite = packageParser_.GetForceWrite();
130     bool combine = packageParser_.GetCombine();
131     string output = packageParser_.GetOutput();
132     string resourcesPath = FileEntry::FilePath(output).Append(RESOURCES_DIR).GetPath();
133     if (ResourceUtil::FileExist(resourcesPath)) {
134         if (!forceWrite) {
135             cerr << "Error: output path exists." << NEW_LINE_PATH << resourcesPath << endl;
136             return RESTOOL_ERROR;
137         }
138 
139         if (!ResourceUtil::RmoveAllDir(resourcesPath)) {
140             return combine ? RESTOOL_SUCCESS : RESTOOL_ERROR;
141         }
142     }
143     return RESTOOL_SUCCESS;
144 }
145 
GenerateHeader() const146 uint32_t ResourcePack::GenerateHeader() const
147 {
148     auto headerPaths = packageParser_.GetResourceHeaders();
149     string textPath = FileEntry::FilePath(packageParser_.GetOutput()).Append("ResourceTable.txt").GetPath();
150     headerPaths.push_back(textPath);
151     for (const auto &headerPath : headerPaths) {
152         string extension = FileEntry::FilePath(headerPath).GetExtension();
153         auto it = headerCreaters_.find(extension);
154         if (it == headerCreaters_.end()) {
155             cout << "Warning: don't support header file format '" << headerPath << "'" << endl;
156             continue;
157         }
158         if (it->second(headerPath) != RESTOOL_SUCCESS) {
159             return RESTOOL_ERROR;
160         }
161     }
162     return RESTOOL_SUCCESS;
163 }
164 
InitConfigJson()165 uint32_t ResourcePack::InitConfigJson()
166 {
167     string config = packageParser_.GetConfig();
168     if (config.empty()) {
169         if (packageParser_.GetInputs().size() > 1) {
170             cerr << "Error: more input path, -j config.json empty" << endl;
171             return RESTOOL_ERROR;
172         }
173         config = ResourceUtil::GetMainPath(packageParser_.GetInputs()[0]).Append(CONFIG_JSON).GetPath();
174         if (!ResourceUtil::FileExist(config)) {
175             config = ResourceUtil::GetMainPath(packageParser_.GetInputs()[0]).Append(MODULE_JSON).GetPath();
176         }
177     }
178 
179     if (FileEntry::FilePath(config).GetFilename() == MODULE_JSON) {
180         ConfigParser::SetUseModule();
181     }
182     configJson_ = ConfigParser(config);
183     if (configJson_.Init() != RESTOOL_SUCCESS) {
184         return RESTOOL_ERROR;
185     }
186     return RESTOOL_SUCCESS;
187 }
188 
GenerateTextHeader(const string &headerPath) const189 uint32_t ResourcePack::GenerateTextHeader(const string &headerPath) const
190 {
191     Header textHeader(headerPath);
192     bool first = true;
193     uint32_t result = textHeader.Create([](stringstream &buffer) {},
194         [&first](stringstream &buffer, const ResourceId& resourceId) {
195             if (first) {
196                 first = false;
197             } else {
198                 buffer << "\n";
199             }
200             buffer << resourceId.type << " " << resourceId.name;
201             buffer << " 0x" << hex << setw(8)  << setfill('0') << resourceId.id;
202         }, [](stringstream &buffer) {});
203     if (result != RESTOOL_SUCCESS) {
204         return RESTOOL_ERROR;
205     }
206     return RESTOOL_SUCCESS;
207 }
208 
GenerateCplusHeader(const string &headerPath) const209 uint32_t ResourcePack::GenerateCplusHeader(const string &headerPath) const
210 {
211     Header cplusHeader(headerPath);
212     uint32_t result = cplusHeader.Create([](stringstream &buffer) {
213         buffer << Header::LICENSE_HEADER << "\n";
214         buffer << "#ifndef RESOURCE_TABLE_H\n";
215         buffer << "#define RESOURCE_TABLE_H\n\n";
216         buffer << "#include<stdint.h>\n\n";
217         buffer << "namespace OHOS {\n";
218     }, [](stringstream &buffer, const ResourceId& resourceId) {
219         string name = resourceId.type + "_" + resourceId.name;
220         transform(name.begin(), name.end(), name.begin(), ::toupper);
221         buffer << "const int32_t " << name << " = ";
222         buffer << "0x" << hex << setw(8)  << setfill('0') << resourceId.id << ";\n";
223     }, [](stringstream &buffer) {
224         buffer << "}\n";
225         buffer << "#endif";
226     });
227     return result;
228 }
229 
GenerateJsHeader(const std::string &headerPath) const230 uint32_t ResourcePack::GenerateJsHeader(const std::string &headerPath) const
231 {
232     Header JsHeader(headerPath);
233     string itemType;
234     uint32_t result = JsHeader.Create([](stringstream &buffer) {
235         buffer << Header::LICENSE_HEADER << "\n";
236         buffer << "export default {\n";
237     }, [&itemType](stringstream &buffer, const ResourceId& resourceId) {
238         if (itemType != resourceId.type) {
239             if (!itemType.empty()) {
240                 buffer << "\n" << "    " << "},\n";
241             }
242             buffer << "    " << resourceId.type << " : {\n";
243             itemType = resourceId.type;
244         } else {
245             buffer << ",\n";
246         }
247         buffer << "        " << resourceId.name << " : " << resourceId.id;
248     }, [](stringstream &buffer) {
249         buffer << "\n" << "    " << "}\n";
250         buffer << "}\n";
251     });
252     return result;
253 }
254 
GenerateConfigJson()255 uint32_t ResourcePack::GenerateConfigJson()
256 {
257     if (configJson_.ParseRefence() != RESTOOL_SUCCESS) {
258         return RESTOOL_ERROR;
259     }
260     string outputPath = FileEntry::FilePath(packageParser_.GetOutput())
261         .Append(ConfigParser::GetConfigName()).GetPath();
262     return configJson_.Save(outputPath);
263 }
264 
CheckConfigJson()265 void ResourcePack::CheckConfigJson()
266 {
267     ResourceCheck resourceCheck(configJson_.GetCheckNode());
268     resourceCheck.CheckConfigJson();
269 }
270 
ScanResources(const vector<string> &inputs, const string &output)271 uint32_t ResourcePack::ScanResources(const vector<string> &inputs, const string &output)
272 {
273     auto &fileManager = FileManager::GetInstance();
274     fileManager.SetModuleName(moduleName_);
275     if (fileManager.ScanModules(inputs, output, configJson_.IsHar()) != RESTOOL_SUCCESS) {
276         return RESTOOL_ERROR;
277     }
278     return RESTOOL_SUCCESS;
279 }
280 
PackNormal()281 uint32_t ResourcePack::PackNormal()
282 {
283     if (InitCompression() != RESTOOL_SUCCESS) {
284         return RESTOOL_ERROR;
285     }
286 
287     if (Init() != RESTOOL_SUCCESS) {
288         return RESTOOL_ERROR;
289     }
290 
291     ResourceMerge resourceMerge;
292     if (resourceMerge.Init() != RESTOOL_SUCCESS) {
293         return RESTOOL_ERROR;
294     }
295 
296     BinaryFilePacker rawFilePacker(packageParser_, moduleName_);
297     std::future<uint32_t> copyFuture = rawFilePacker.CopyBinaryFileAsync(resourceMerge.GetInputs());
298     uint32_t packQualifierRet = PackQualifierResources(resourceMerge);
299     if (packQualifierRet != RESTOOL_SUCCESS) {
300         rawFilePacker.StopCopy();
301     }
302     uint32_t copyRet = copyFuture.get();
303     return packQualifierRet == RESTOOL_SUCCESS && copyRet == RESTOOL_SUCCESS ? RESTOOL_SUCCESS : RESTOOL_ERROR;
304 }
305 
PackQualifierResources(const ResourceMerge &resourceMerge)306 uint32_t ResourcePack::PackQualifierResources(const ResourceMerge &resourceMerge)
307 {
308     if (ScanResources(resourceMerge.GetInputs(), packageParser_.GetOutput()) != RESTOOL_SUCCESS) {
309         return RESTOOL_ERROR;
310     }
311 
312     if (GenerateHeader() != RESTOOL_SUCCESS) {
313         return RESTOOL_ERROR;
314     }
315 
316     if (GenerateConfigJson() != RESTOOL_SUCCESS) {
317         return RESTOOL_ERROR;
318     }
319 
320     if (!FileManager::GetInstance().ScaleIcons(packageParser_.GetOutput(), configJson_.GetCheckNode())) {
321         return RESTOOL_ERROR;
322     }
323 
324     if (packageParser_.GetIconCheck()) {
325         CheckConfigJson();
326     }
327 
328     ResourceTable resourceTable;
329     if (!packageParser_.GetDependEntry().empty()) {
330         if (HandleFeature() != RESTOOL_SUCCESS) {
331             return RESTOOL_ERROR;
332         }
333         if (GenerateHeader() != RESTOOL_SUCCESS) {
334             return RESTOOL_ERROR;
335         }
336     }
337 
338     if (resourceTable.CreateResourceTable() != RESTOOL_SUCCESS) {
339         return RESTOOL_ERROR;
340     }
341     return RESTOOL_SUCCESS;
342 }
343 
HandleFeature()344 uint32_t ResourcePack::HandleFeature()
345 {
346     string output = packageParser_.GetOutput();
347     string featureDependEntry = packageParser_.GetDependEntry();
348     if (featureDependEntry.empty()) {
349         return RESTOOL_SUCCESS;
350     }
351     string jsonFile = FileEntry::FilePath(featureDependEntry).Append(CONFIG_JSON).GetPath();
352     ConfigParser entryJson(jsonFile);
353     entryJson.SetDependEntry(true);
354     if (entryJson.Init() != RESTOOL_SUCCESS) {
355         cerr << "Error: config json invalid." << NEW_LINE_PATH << jsonFile << endl;
356         return RESTOOL_ERROR;
357     }
358 
359     int64_t labelId = entryJson.GetAbilityLabelId();
360     int64_t iconId = entryJson.GetAbilityIconId();
361     if (labelId <= 0 || iconId <= 0) {
362         cerr << "Error: Entry MainAbility must have 'icon' and 'label'." << endl;
363         return RESTOOL_ERROR;
364     }
365     string path = FileEntry::FilePath(featureDependEntry).Append(RESOURCE_INDEX_FILE).GetPath();
366     map<int64_t, vector<ResourceItem>> resInfoLocal;
367     ResourceTable resourceTable;
368     if (resourceTable.LoadResTable(path, resInfoLocal) != RESTOOL_SUCCESS) {
369         cerr << "Error: LoadResTable fail." << endl;
370         return RESTOOL_ERROR;
371     }
372     jsonFile = FileEntry::FilePath(output).Append(CONFIG_JSON).GetPath();
373     ConfigParser config(jsonFile);
374     if (config.Init() != RESTOOL_SUCCESS) {
375         cerr << "Error: config json invalid." << NEW_LINE_PATH << jsonFile << endl;
376         return RESTOOL_ERROR;
377     }
378     vector<ResourceItem> items;
379     if (FindResourceItems(resInfoLocal, items, labelId) != RESTOOL_SUCCESS ||
380         HandleLabel(items, config) != RESTOOL_SUCCESS) {
381         return RESTOOL_ERROR;
382     }
383     items.clear();
384     if (FindResourceItems(resInfoLocal, items, iconId) != RESTOOL_SUCCESS ||
385         HandleIcon(items, config) != RESTOOL_SUCCESS) {
386         return RESTOOL_ERROR;
387     }
388     string outputPath = FileEntry::FilePath(output).Append(ConfigParser::GetConfigName()).GetPath();
389     if (config.Save(outputPath) != RESTOOL_SUCCESS) {
390         return RESTOOL_ERROR;
391     }
392     entryJson.SetDependEntry(false);
393     return RESTOOL_SUCCESS;
394 }
395 
FindResourceItems(const map<int64_t, vector<ResourceItem>> &resInfoLocal, vector<ResourceItem> &items, int64_t id) const396 uint32_t ResourcePack::FindResourceItems(const map<int64_t, vector<ResourceItem>> &resInfoLocal,
397                                          vector<ResourceItem> &items, int64_t id) const
398 {
399     auto ret = resInfoLocal.find(id);
400     if (ret == resInfoLocal.end()) {
401         cerr << "Error: FindResourceItems don't found '" << id << "'." << endl;
402         return RESTOOL_ERROR;
403     }
404     ResType type = ResType::INVALID_RES_TYPE;
405     items = ret->second;
406     if (items.empty()) {
407         cerr << "Error: FindResourceItems resource item empty '" << id << "'." << endl;
408         return RESTOOL_ERROR;
409     }
410     for (auto &it : items) {
411         if (type == ResType::INVALID_RES_TYPE) {
412             type = it.GetResType();
413         }
414         if (type != it.GetResType()) {
415             cerr << "Error: FindResourceItems invalid restype '" << ResourceUtil::ResTypeToString(type);
416             cerr << "' vs '"  << ResourceUtil::ResTypeToString(it.GetResType()) << "'." << endl;
417             return RESTOOL_ERROR;
418         }
419     }
420     return RESTOOL_SUCCESS;
421 }
422 
HandleLabel(vector<ResourceItem> &items, ConfigParser &config) const423 uint32_t ResourcePack::HandleLabel(vector<ResourceItem> &items, ConfigParser &config) const
424 {
425     int64_t nextId = 0;
426     string idName;
427     for (auto it : items) {
428         if (it.GetResType() != ResType::STRING) {
429             cerr << "Error: HandleLabel invalid restype '";
430             cerr << ResourceUtil::ResTypeToString(it.GetResType()) << "'." << endl;
431             return RESTOOL_ERROR;
432         }
433         idName = it.GetName() + "_entry";
434         it.SetName(idName);
435         string data(reinterpret_cast<const char *>(it.GetData()));
436         if (it.GetDataLength() - 1 < 0) {
437             return RESTOOL_ERROR;
438         }
439         if (!it.SetData(reinterpret_cast<const int8_t *>(data.c_str()), it.GetDataLength() - 1)) {
440             return RESTOOL_ERROR;
441         }
442         if (nextId <= 0) {
443             nextId = IdWorker::GetInstance().GenerateId(ResType::STRING, idName);
444         }
445         SaveResourceItem(it, nextId);
446     }
447     string label = "$string:" +idName;
448     config.SetAppLabel(label, nextId);
449     return RESTOOL_SUCCESS;
450 }
451 
CopyIcon(string &dataPath, const string &idName, string &fileName) const452 bool ResourcePack::CopyIcon(string &dataPath, const string &idName, string &fileName) const
453 {
454     string featureDependEntry = packageParser_.GetDependEntry();
455     string source = FileEntry::FilePath(featureDependEntry).Append(dataPath).GetPath();
456     string suffix = FileEntry::FilePath(source).GetExtension();
457     fileName = idName + suffix;
458     string output = packageParser_.GetOutput();
459 #ifdef _WIN32
460     ResourceUtil::StringReplace(dataPath, SEPARATOR, WIN_SEPARATOR);
461 #endif
462     string dstDir = FileEntry::FilePath(output).Append(dataPath).GetParent().GetPath();
463     string dst = FileEntry::FilePath(dstDir).Append(fileName).GetPath();
464     if (!ResourceUtil::CreateDirs(dstDir)) {
465         cerr << "Error: Create Dirs fail '" << dstDir << "'."<< endl;
466         return false;
467     }
468     if (!ResourceUtil::CopyFileInner(source, dst)) {
469         cerr << "Error: copy file fail from '" << source << "' to '" << dst << "'." << endl;
470         return false;
471     }
472     return true;
473 }
474 
HandleIcon(vector<ResourceItem> &items, ConfigParser &config) const475 uint32_t ResourcePack::HandleIcon(vector<ResourceItem> &items, ConfigParser &config) const
476 {
477     int64_t nextId = 0;
478     string idName;
479     for (auto it : items) {
480         if (it.GetResType() != ResType::MEDIA) {
481             cerr << "Error: HandleLabel invalid restype '";
482             cerr << ResourceUtil::ResTypeToString(it.GetResType()) << "'." << endl;
483             return RESTOOL_ERROR;
484         }
485         string dataPath(reinterpret_cast<const char *>(it.GetData()));
486         string::size_type pos = dataPath.find_first_of(SEPARATOR);
487         if (pos == string::npos) {
488             cerr << "Error: HandleIcon invalid '" << dataPath << "'."<< endl;
489             return RESTOOL_ERROR;
490         }
491         dataPath = dataPath.substr(pos + 1);
492         idName = it.GetName() + "_entry";
493         string fileName;
494         if (!CopyIcon(dataPath, idName, fileName)) {
495             return RESTOOL_ERROR;
496         }
497         string data = FileEntry::FilePath(moduleName_).Append(dataPath).GetParent().Append(fileName).GetPath();
498         ResourceUtil::StringReplace(data, WIN_SEPARATOR, SEPARATOR);
499         ResourceItem resourceItem(fileName, it.GetKeyParam(), ResType::MEDIA);
500         resourceItem.SetLimitKey(it.GetLimitKey());
501         if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(data.c_str()), data.length())) {
502             return RESTOOL_ERROR;
503         }
504         if (nextId <= 0) {
505             nextId = IdWorker::GetInstance().GenerateId(ResType::MEDIA, idName);
506         }
507         SaveResourceItem(resourceItem, nextId);
508     }
509     string icon = "$media:" + idName;
510     config.SetAppIcon(icon, nextId);
511     return RESTOOL_SUCCESS;
512 }
513 
SaveResourceItem(const ResourceItem &resourceItem, int64_t nextId) const514 void ResourcePack::SaveResourceItem(const ResourceItem &resourceItem, int64_t nextId) const
515 {
516     map<int64_t, vector<ResourceItem>> resInfo;
517     vector<ResourceItem> vet;
518     vet.push_back(resourceItem);
519     resInfo.insert(make_pair(nextId, vet));
520     FileManager &fileManager = FileManager::GetInstance();
521     fileManager.MergeResourceItem(resInfo);
522 }
523 
PackAppend()524 uint32_t ResourcePack::PackAppend()
525 {
526     ResourceAppend resourceAppend(packageParser_);
527     if (!packageParser_.GetAppend().empty()) {
528         return resourceAppend.Append();
529     }
530     return RESTOOL_SUCCESS;
531 }
532 
PackCombine()533 uint32_t ResourcePack::PackCombine()
534 {
535     if (Init() != RESTOOL_SUCCESS) {
536         return RESTOOL_ERROR;
537     }
538 
539     ResourceAppend resourceAppend(packageParser_);
540     if (resourceAppend.Combine() != RESTOOL_SUCCESS) {
541         return RESTOOL_ERROR;
542     }
543 
544     if (GenerateConfigJson() != RESTOOL_SUCCESS) {
545         return RESTOOL_ERROR;
546     }
547 
548     if (packageParser_.GetIconCheck()) {
549         CheckConfigJsonForCombine(resourceAppend);
550     }
551 
552     if (GenerateHeader() != RESTOOL_SUCCESS) {
553         return RESTOOL_ERROR;
554     }
555     return RESTOOL_SUCCESS;
556 }
557 
CheckConfigJsonForCombine(ResourceAppend &resourceAppend)558 void ResourcePack::CheckConfigJsonForCombine(ResourceAppend &resourceAppend)
559 {
560     ResourceCheck resourceCheck(configJson_.GetCheckNode(), make_shared<ResourceAppend>(resourceAppend));
561     resourceCheck.CheckConfigJsonForCombine();
562 }
563 
564 }
565 }
566 }
567