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
28namespace OHOS {
29namespace Global {
30namespace Restool {
31using namespace std;
32ResourcePack::ResourcePack(const PackageParser &packageParser):packageParser_(packageParser)
33{
34}
35
36uint32_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
48uint32_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
61uint32_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
78uint32_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
119void 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
127uint32_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
146uint32_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
165uint32_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
189uint32_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
209uint32_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
230uint32_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
255uint32_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
265void ResourcePack::CheckConfigJson()
266{
267    ResourceCheck resourceCheck(configJson_.GetCheckNode());
268    resourceCheck.CheckConfigJson();
269}
270
271uint32_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
281uint32_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
306uint32_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
344uint32_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
396uint32_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
423uint32_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
452bool 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
475uint32_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
514void 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
524uint32_t ResourcePack::PackAppend()
525{
526    ResourceAppend resourceAppend(packageParser_);
527    if (!packageParser_.GetAppend().empty()) {
528        return resourceAppend.Append();
529    }
530    return RESTOOL_SUCCESS;
531}
532
533uint32_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
558void ResourcePack::CheckConfigJsonForCombine(ResourceAppend &resourceAppend)
559{
560    ResourceCheck resourceCheck(configJson_.GetCheckNode(), make_shared<ResourceAppend>(resourceAppend));
561    resourceCheck.CheckConfigJsonForCombine();
562}
563
564}
565}
566}
567