SnapmaticPicture: add G5EX RagePhotoFormatParser
This commit is contained in:
		
							parent
							
								
									bb62cc438d
								
							
						
					
					
						commit
						83e133ddf2
					
				
					 2 changed files with 140 additions and 50 deletions
				
			
		|  | @ -61,8 +61,10 @@ inline void gta5view_uInt32ToCharLE(quint32 x, char *y) | ||||||
|     y[3] = x >> 24; |     y[3] = x >> 24; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| inline bool gta5view_export_load(const QByteArray &fileData, RagePhoto *ragePhoto) | ragephoto_bool_t gta5view_export_load(RagePhotoData *rp_data, const char *data, size_t length) | ||||||
| { | { | ||||||
|  |     const QByteArray fileData = QByteArray::fromRawData(data, length); | ||||||
|  | 
 | ||||||
|     QBuffer dataBuffer; |     QBuffer dataBuffer; | ||||||
|     dataBuffer.setData(fileData); |     dataBuffer.setData(fileData); | ||||||
|     if (!dataBuffer.open(QIODevice::ReadOnly)) |     if (!dataBuffer.open(QIODevice::ReadOnly)) | ||||||
|  | @ -70,181 +72,268 @@ inline bool gta5view_export_load(const QByteArray &fileData, RagePhoto *ragePhot | ||||||
|     dataBuffer.seek(4); |     dataBuffer.seek(4); | ||||||
| 
 | 
 | ||||||
|     char uInt32Buffer[4]; |     char uInt32Buffer[4]; | ||||||
|     qint64 size = dataBuffer.read(uInt32Buffer, 4); |     size_t size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|     if (size != 4) |     if (size != 4) { | ||||||
|  |         rp_data->error = RagePhoto::NoFormatIdentifier; | ||||||
|         return false; |         return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     quint32 format = gta5view_charToUInt32LE(uInt32Buffer); |     uint32_t format = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
|     if (format == G5EExportFormat::G5E3P) { |     if (format == G5EExportFormat::G5E3P) { | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteHeader; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         quint32 compressedSize = gta5view_charToUInt32LE(uInt32Buffer); |         quint32 compressedSize = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         char *compressedPhotoHeader = static_cast<char*>(std::malloc(compressedSize)); |         char *compressedPhotoHeader = static_cast<char*>(std::malloc(compressedSize)); | ||||||
|         if (!compressedPhotoHeader) |         if (!compressedPhotoHeader) { | ||||||
|  |             rp_data->error = RagePhoto::HeaderMallocError; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         size = dataBuffer.read(compressedPhotoHeader, compressedSize); |         size = dataBuffer.read(compressedPhotoHeader, compressedSize); | ||||||
|         if (size != compressedSize) { |         if (size != compressedSize) { | ||||||
|             std::free(compressedPhotoHeader); |             std::free(compressedPhotoHeader); | ||||||
|  |             rp_data->error = RagePhoto::UnicodeHeaderError; | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         QByteArray t_photoHeader = QByteArray::fromRawData(compressedPhotoHeader, compressedSize); |         QByteArray t_photoHeader = QByteArray::fromRawData(compressedPhotoHeader, compressedSize); | ||||||
|         t_photoHeader = qUncompress(t_photoHeader); |         t_photoHeader = qUncompress(t_photoHeader); | ||||||
|         std::free(compressedPhotoHeader); |         std::free(compressedPhotoHeader); | ||||||
|         if (t_photoHeader.isEmpty()) |         if (t_photoHeader.isEmpty()) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteHeader; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteHeader; | ||||||
|             return false; |             return false; | ||||||
|         quint32 t_headerSum = gta5view_charToUInt32LE(uInt32Buffer); |         } | ||||||
|         ragePhoto->setHeader(t_photoHeader.constData(), t_headerSum); |         rp_data->headerSum = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
|  |         rp_data->header = static_cast<char*>(std::malloc(t_photoHeader.size() + 1)); | ||||||
|  |         if (!rp_data->header) { | ||||||
|  |             rp_data->error = RagePhoto::HeaderMallocError; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         std::memcpy(rp_data->header, t_photoHeader.constData(), t_photoHeader.size() + 1); | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompletePhotoBuffer; | ||||||
|             return false; |             return false; | ||||||
|         quint32 t_photoBuffer = gta5view_charToUInt32LE(uInt32Buffer); |         } | ||||||
|  |         rp_data->jpegBuffer = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompletePhotoBuffer; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); |         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         char *compressedPhoto = static_cast<char*>(std::malloc(compressedSize)); |         char *compressedPhoto = static_cast<char*>(std::malloc(compressedSize)); | ||||||
|         if (!compressedPhoto) |         if (!compressedPhoto) { | ||||||
|  |             rp_data->error = RagePhoto::PhotoMallocError; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         size = dataBuffer.read(compressedPhoto, compressedSize); |         size = dataBuffer.read(compressedPhoto, compressedSize); | ||||||
|         if (size != compressedSize) { |         if (size != compressedSize) { | ||||||
|             std::free(compressedPhoto); |             std::free(compressedPhoto); | ||||||
|  |             rp_data->error = RagePhoto::PhotoReadError; | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         QByteArray t_photoData = QByteArray::fromRawData(compressedPhoto, compressedSize); |         QByteArray t_photoData = QByteArray::fromRawData(compressedPhoto, compressedSize); | ||||||
|         t_photoData = qUncompress(t_photoData); |         t_photoData = qUncompress(t_photoData); | ||||||
|         std::free(compressedPhoto); |         std::free(compressedPhoto); | ||||||
|         ragePhoto->setPhoto(t_photoData.constData(), t_photoData.size(), t_photoBuffer); |         rp_data->jpeg = static_cast<char*>(std::malloc(t_photoData.size())); | ||||||
|  |         if (!rp_data->jpeg) { | ||||||
|  |             rp_data->error = RagePhoto::PhotoMallocError; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         std::memcpy(rp_data->jpeg, t_photoData.constData(), t_photoData.size()); | ||||||
|  |         rp_data->jpegSize = t_photoData.size(); | ||||||
| 
 | 
 | ||||||
|         // JSON offset will be calculated later, offsets will be removed in G5E4P
 |         // JSON offset will be calculated later, offsets will be removed in G5E4P
 | ||||||
|         size = dataBuffer.skip(4); |         size = dataBuffer.skip(4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteJsonOffset; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteJsonBuffer; | ||||||
|             return false; |             return false; | ||||||
|         quint32 t_jsonBuffer = gta5view_charToUInt32LE(uInt32Buffer); |         } | ||||||
|  |         rp_data->jsonBuffer = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteJsonBuffer; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); |         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         char *compressedJson = static_cast<char*>(std::malloc(compressedSize)); |         char *compressedJson = static_cast<char*>(std::malloc(compressedSize)); | ||||||
|         if (!compressedJson) |         if (!compressedJson) { | ||||||
|  |             rp_data->error = RagePhoto::JsonMallocError; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         size = dataBuffer.read(compressedJson, compressedSize); |         size = dataBuffer.read(compressedJson, compressedSize); | ||||||
|         if (size != compressedSize) { |         if (size != compressedSize) { | ||||||
|             std::free(compressedJson); |             std::free(compressedJson); | ||||||
|  |             rp_data->error = RagePhoto::JsonReadError; | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         QByteArray t_jsonData = QByteArray::fromRawData(compressedJson, compressedSize); |         QByteArray t_jsonData = QByteArray::fromRawData(compressedJson, compressedSize); | ||||||
|         t_jsonData = qUncompress(t_jsonData); |         t_jsonData = qUncompress(t_jsonData); | ||||||
|         std::free(compressedJson); |         std::free(compressedJson); | ||||||
|         if (t_jsonData.isEmpty()) |         if (t_jsonData.isEmpty()) { | ||||||
|  |             rp_data->error = RagePhoto::JsonReadError; | ||||||
|             return false; |             return false; | ||||||
|         ragePhoto->setJson(t_jsonData.constData(), t_jsonBuffer); |         } | ||||||
|  |         rp_data->json = static_cast<char*>(std::malloc(t_jsonData.size() + 1)); | ||||||
|  |         if (!rp_data->json) { | ||||||
|  |             rp_data->error = RagePhoto::JsonMallocError; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         std::memcpy(rp_data->json, t_jsonData.constData(), t_jsonData.size() + 1); | ||||||
| 
 | 
 | ||||||
|         // TITL offset will be calculated later, offsets will be removed in G5E4P
 |         // TITL offset will be calculated later, offsets will be removed in G5E4P
 | ||||||
|         size = dataBuffer.skip(4); |         size = dataBuffer.skip(4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteTitleOffset; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteTitleBuffer; | ||||||
|             return false; |             return false; | ||||||
|         quint32 t_titlBuffer = gta5view_charToUInt32LE(uInt32Buffer); |         } | ||||||
|  |         rp_data->titlBuffer = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteTitleBuffer; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); |         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         char *compressedTitl = static_cast<char*>(std::malloc(compressedSize)); |         char *compressedTitl = static_cast<char*>(std::malloc(compressedSize)); | ||||||
|         if (!compressedTitl) |         if (!compressedTitl) { | ||||||
|  |             rp_data->error = RagePhoto::TitleMallocError; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         size = dataBuffer.read(compressedTitl, compressedSize); |         size = dataBuffer.read(compressedTitl, compressedSize); | ||||||
|         if (size != compressedSize) { |         if (size != compressedSize) { | ||||||
|             std::free(compressedTitl); |             std::free(compressedTitl); | ||||||
|  |             rp_data->error = RagePhoto::TitleReadError; | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         QByteArray t_titlData = QByteArray::fromRawData(compressedTitl, compressedSize); |         QByteArray t_titlData = QByteArray::fromRawData(compressedTitl, compressedSize); | ||||||
|         t_titlData = qUncompress(t_titlData); |         t_titlData = qUncompress(t_titlData); | ||||||
|         std::free(compressedTitl); |         std::free(compressedTitl); | ||||||
|         ragePhoto->setTitle(t_titlData.constData(), t_titlBuffer); |         rp_data->title = static_cast<char*>(std::malloc(t_titlData.size() + 1)); | ||||||
|  |         if (!rp_data->title) { | ||||||
|  |             rp_data->error = RagePhoto::TitleMallocError; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         std::memcpy(rp_data->title, t_titlData.constData(), t_titlData.size() + 1); | ||||||
| 
 | 
 | ||||||
|         // DESC offset will be calculated later, offsets will be removed in G5E4P
 |         // DESC offset will be calculated later, offsets will be removed in G5E4P
 | ||||||
|         size = dataBuffer.skip(4); |         size = dataBuffer.skip(4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteDescOffset; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteDescBuffer; | ||||||
|             return false; |             return false; | ||||||
|         quint32 t_descBuffer = gta5view_charToUInt32LE(uInt32Buffer); |         } | ||||||
|  |         rp_data->descBuffer = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.read(uInt32Buffer, 4); |         size = dataBuffer.read(uInt32Buffer, 4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteDescBuffer; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); |         compressedSize = gta5view_charToUInt32LE(uInt32Buffer); | ||||||
| 
 | 
 | ||||||
|         char *compressedDesc = static_cast<char*>(std::malloc(compressedSize)); |         char *compressedDesc = static_cast<char*>(std::malloc(compressedSize)); | ||||||
|         if (!compressedDesc) |         if (!compressedDesc) { | ||||||
|  |             rp_data->error = RagePhoto::DescMallocError; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         size = dataBuffer.read(compressedDesc, compressedSize); |         size = dataBuffer.read(compressedDesc, compressedSize); | ||||||
|         if (size != compressedSize) { |         if (size != compressedSize) { | ||||||
|             std::free(compressedDesc); |             std::free(compressedDesc); | ||||||
|  |             rp_data->error = RagePhoto::DescReadError; | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         QByteArray t_descData = QByteArray::fromRawData(compressedDesc, compressedSize); |         QByteArray t_descData = QByteArray::fromRawData(compressedDesc, compressedSize); | ||||||
|         t_descData  = qUncompress(t_descData); |         t_descData = qUncompress(t_descData); | ||||||
|         std::free(compressedDesc); |         std::free(compressedDesc); | ||||||
|         ragePhoto->setDescription(t_descData.constData(), t_descBuffer); |         rp_data->description = static_cast<char*>(std::malloc(t_descData.size() + 1)); | ||||||
|  |         if (!rp_data->description) { | ||||||
|  |             rp_data->error = RagePhoto::DescMallocError; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         std::memcpy(rp_data->description, t_descData.constData(), t_descData.size() + 1); | ||||||
| 
 | 
 | ||||||
|         // EOF will be calculated later, EOF marker will be removed in G5E4P
 |         // EOF will be calculated later, EOF marker will be removed in G5E4P
 | ||||||
|         size = dataBuffer.skip(4); |         size = dataBuffer.skip(4); | ||||||
|         if (size != 4) |         if (size != 4) { | ||||||
|  |             rp_data->error = RagePhoto::IncompleteEOF; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         // libragephoto needs to know we gave it a GTA V Snapmatic
 |         // libragephoto needs to know we gave it a GTA V Snapmatic
 | ||||||
|         ragePhoto->setFormat(RagePhoto::GTA5); |         rp_data->photoFormat = RagePhoto::GTA5; | ||||||
|  |         rp_data->error = RagePhoto::NoError; | ||||||
|  |         RagePhoto::setBufferOffsets(rp_data); | ||||||
| 
 | 
 | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|     else if (format == G5EExportFormat::G5E2P) { |     else if (format == G5EExportFormat::G5E2P) { | ||||||
|         const QByteArray t_fileData = qUncompress(dataBuffer.readAll()); |         const QByteArray t_fileData = qUncompress(dataBuffer.readAll()); | ||||||
|         if (t_fileData.isEmpty()) |         if (t_fileData.isEmpty()) { | ||||||
|  |             rp_data->error = RagePhoto::IncompatibleFormat; | ||||||
|             return false; |             return false; | ||||||
|         return ragePhoto->load(t_fileData.constData(), t_fileData.size()); |         } | ||||||
|  |         return RagePhoto::load(rp_data, nullptr, t_fileData.constData(), t_fileData.size()); | ||||||
|     } |     } | ||||||
|     else if (format == G5EExportFormat::G5E1P) { |     else if (format == G5EExportFormat::G5E1P) { | ||||||
|         size = dataBuffer.skip(1); |         size = dataBuffer.skip(1); | ||||||
|         if (size != 1) |         if (size != 1) { | ||||||
|  |             rp_data->error = RagePhoto::IncompatibleFormat; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         char length[1]; |         char length[1]; | ||||||
|         size = dataBuffer.read(length, 1); |         size = dataBuffer.read(length, 1); | ||||||
|         if (size != 1) |         if (size != 1) { | ||||||
|  |             rp_data->error = RagePhoto::IncompatibleFormat; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
|         int i_length = QByteArray::number(static_cast<int>(length[0]), 16).toInt() + 6; |         int i_length = QByteArray::number(static_cast<int>(length[0]), 16).toInt() + 6; | ||||||
| 
 | 
 | ||||||
|         size = dataBuffer.skip(i_length); |         size = dataBuffer.skip(i_length); | ||||||
|         if (size != i_length) |         if (size != i_length) { | ||||||
|  |             rp_data->error = RagePhoto::IncompatibleFormat; | ||||||
|             return false; |             return false; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         const QByteArray t_fileData = qUncompress(dataBuffer.readAll()); |         const QByteArray t_fileData = qUncompress(dataBuffer.readAll()); | ||||||
|         if (t_fileData.isEmpty()) |         if (t_fileData.isEmpty()) { | ||||||
|  |             rp_data->error = RagePhoto::IncompatibleFormat; | ||||||
|             return false; |             return false; | ||||||
|         return ragePhoto->load(t_fileData.constData(), t_fileData.size()); |         } | ||||||
|  |         return RagePhoto::load(rp_data, nullptr, t_fileData.constData(), t_fileData.size()); | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
|  | @ -320,6 +409,11 @@ inline void gta5view_export_save(QIODevice *ioDevice, RagePhotoData *data) | ||||||
| // SNAPMATIC PICTURE CLASS
 | // SNAPMATIC PICTURE CLASS
 | ||||||
| SnapmaticPicture::SnapmaticPicture(const QString &fileName, QObject *parent) : QObject(parent), picFilePath(fileName) | SnapmaticPicture::SnapmaticPicture(const QString &fileName, QObject *parent) : QObject(parent), picFilePath(fileName) | ||||||
| { | { | ||||||
|  |     RagePhotoFormatParser g5eParser[1]{}; | ||||||
|  |     g5eParser[0].photoFormat = G5EPhotoFormat::G5EX; | ||||||
|  |     g5eParser[0].funcLoad = >a5view_export_load; | ||||||
|  |     p_ragePhoto.addParser(&g5eParser[0]); | ||||||
|  | 
 | ||||||
|     reset(); |     reset(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -372,12 +466,8 @@ bool SnapmaticPicture::preloadFile() | ||||||
|     bool ok = p_ragePhoto.load(fileData.constData(), fileData.size()); |     bool ok = p_ragePhoto.load(fileData.constData(), fileData.size()); | ||||||
|     picFormat = p_ragePhoto.format(); |     picFormat = p_ragePhoto.format(); | ||||||
| 
 | 
 | ||||||
|     // libragephoto doesn't support modules yet
 |  | ||||||
|     if (picFormat == G5EPhotoFormat::G5EX) |  | ||||||
|         ok = gta5view_export_load(fileData, &p_ragePhoto); |  | ||||||
| 
 |  | ||||||
|     if (!ok) { |     if (!ok) { | ||||||
|         const RagePhoto::Error error = static_cast<RagePhoto::Error>(p_ragePhoto.error()); |         const int32_t error = p_ragePhoto.error(); | ||||||
|         switch (error) { |         switch (error) { | ||||||
|         case RagePhoto::Uninitialised: |         case RagePhoto::Uninitialised: | ||||||
|             lastStep = "1;/1,OpenFile," % convertDrawStringForLog(picFilePath); |             lastStep = "1;/1,OpenFile," % convertDrawStringForLog(picFilePath); | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| Subproject commit b2e765c2af5fad634bdc2bbb1eb48ccaeb6a509a | Subproject commit e51d50f77e38a39151c9418bb9a3359971fa5454 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue