Skip to content

Commit 99f3135

Browse files
authored
Merge branch 'ihhub:master' into feature/add-aspect-ratio-info
2 parents 168442b + 275ccfd commit 99f3135

File tree

3 files changed

+173
-46
lines changed

3 files changed

+173
-46
lines changed

src/engine/system.cpp

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/***************************************************************************
22
* fheroes2: https://github.com/ihhub/fheroes2 *
3-
* Copyright (C) 2019 - 2022 *
3+
* Copyright (C) 2019 - 2023 *
44
* *
55
* Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 *
66
* Copyright (C) 2009 by Andrey Afletdinov <[email protected]> *
@@ -24,7 +24,9 @@
2424
#include <algorithm>
2525
#include <cassert>
2626
#include <cstdlib>
27+
#include <filesystem>
2728
#include <initializer_list>
29+
#include <system_error>
2830
#include <utility>
2931

3032
#if defined( _WIN32 )
@@ -166,6 +168,50 @@ namespace
166168

167169
return path;
168170
}
171+
172+
bool globMatch( const std::string_view string, const std::string_view wildcard )
173+
{
174+
size_t stringIdx = 0;
175+
size_t wildcardIdx = 0;
176+
177+
size_t fallbackStringIdx = std::string_view::npos;
178+
size_t fallbackWildcardIdx = std::string_view::npos;
179+
180+
while ( stringIdx < string.length() ) {
181+
const bool isWildcardNotEnded = ( wildcardIdx < wildcard.length() );
182+
183+
if ( isWildcardNotEnded && wildcard[wildcardIdx] == '*' ) {
184+
++wildcardIdx;
185+
186+
fallbackStringIdx = stringIdx;
187+
fallbackWildcardIdx = wildcardIdx;
188+
}
189+
else if ( isWildcardNotEnded && ( wildcard[wildcardIdx] == '?' || wildcard[wildcardIdx] == string[stringIdx] ) ) {
190+
++stringIdx;
191+
++wildcardIdx;
192+
}
193+
else {
194+
if ( fallbackWildcardIdx == std::string_view::npos ) {
195+
return false;
196+
}
197+
198+
assert( fallbackStringIdx != std::string_view::npos );
199+
200+
++fallbackStringIdx;
201+
202+
stringIdx = fallbackStringIdx;
203+
wildcardIdx = fallbackWildcardIdx;
204+
}
205+
}
206+
207+
for ( ; wildcardIdx < wildcard.length(); ++wildcardIdx ) {
208+
if ( wildcard[wildcardIdx] != '*' ) {
209+
break;
210+
}
211+
}
212+
213+
return wildcardIdx == wildcard.length();
214+
}
169215
}
170216

171217
bool System::isHandheldDevice()
@@ -177,6 +223,15 @@ bool System::isHandheldDevice()
177223
#endif
178224
}
179225

226+
bool System::isShellLevelGlobbingSupported()
227+
{
228+
#if defined( _WIN32 )
229+
return false;
230+
#else
231+
return true;
232+
#endif
233+
}
234+
180235
bool System::MakeDirectory( const std::string & path )
181236
{
182237
#if defined( _WIN32 )
@@ -482,6 +537,48 @@ bool System::GetCaseInsensitivePath( const std::string & path, std::string & cor
482537
}
483538
#endif
484539

540+
void System::globFiles( const std::string_view glob, std::vector<std::string> & fileNames )
541+
{
542+
const std::filesystem::path globPath( glob );
543+
544+
std::filesystem::path dir = globPath.parent_path();
545+
if ( dir.empty() ) {
546+
dir = std::filesystem::path{ "." };
547+
}
548+
549+
std::error_code ec;
550+
551+
// Using the non-throwing overload
552+
if ( !std::filesystem::is_directory( dir, ec ) ) {
553+
fileNames.emplace_back( glob );
554+
return;
555+
}
556+
557+
const std::string pattern = globPath.filename().string();
558+
559+
if ( pattern.find( '*' ) == std::string_view::npos && pattern.find( '?' ) == std::string_view::npos ) {
560+
fileNames.emplace_back( glob );
561+
return;
562+
}
563+
564+
bool isNoMatches = true;
565+
566+
// Using the non-throwing overload
567+
for ( const std::filesystem::directory_entry & entry : std::filesystem::directory_iterator( dir, ec ) ) {
568+
const std::filesystem::path & entryPath = entry.path();
569+
570+
if ( globMatch( entryPath.filename().string(), pattern ) ) {
571+
fileNames.push_back( entryPath.string() );
572+
573+
isNoMatches = false;
574+
}
575+
}
576+
577+
if ( isNoMatches ) {
578+
fileNames.emplace_back( glob );
579+
}
580+
}
581+
485582
std::string System::FileNameToUTF8( const std::string & name )
486583
{
487584
#if defined( _WIN32 )

src/engine/system.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/***************************************************************************
22
* fheroes2: https://github.com/ihhub/fheroes2 *
3-
* Copyright (C) 2019 - 2022 *
3+
* Copyright (C) 2019 - 2023 *
44
* *
55
* Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 *
66
* Copyright (C) 2013 by Andrey Afletdinov <[email protected]> *
@@ -32,6 +32,9 @@
3232
namespace System
3333
{
3434
bool isHandheldDevice();
35+
// Returns true if target platform supports shell-level globbing (Unix-like platforms with POSIX-compatible shells).
36+
// Otherwise returns false, which means that app need to resolve wildcard patterns itself (for example, on Windows).
37+
bool isShellLevelGlobbingSupported();
3538

3639
bool MakeDirectory( const std::string & path );
3740
std::string concatPath( const std::string_view left, const std::string_view right );
@@ -49,6 +52,12 @@ namespace System
4952

5053
bool GetCaseInsensitivePath( const std::string & path, std::string & correctedPath );
5154

55+
// Resolves the wildcard pattern 'glob' and appends matching paths to 'fileNames'. Supported wildcards are '?' and '*'.
56+
// These wildcards are resolved only if they are in the last element of the path. For example, they will be resolved
57+
// in the case of the 'foo/b*r?' pattern, but they will be ignored (used as is) in the case of '*/bar' pattern. If there
58+
// are no files matching the pattern, it will be appended to the 'fileNames' as is.
59+
void globFiles( const std::string_view glob, std::vector<std::string> & fileNames );
60+
5261
std::string FileNameToUTF8( const std::string & name );
5362

5463
tm GetTM( const time_t time );

src/tools/icn2img.cpp

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/***************************************************************************
22
* fheroes2: https://github.com/ihhub/fheroes2 *
3-
* Copyright (C) 2022 *
3+
* Copyright (C) 2022 - 2023 *
44
* *
55
* Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 *
66
* Copyright (C) 2009 by Andrey Afletdinov <[email protected]> *
@@ -23,11 +23,14 @@
2323

2424
#include <cstdint>
2525
#include <cstdlib>
26+
#include <filesystem>
2627
#include <iomanip>
2728
#include <iostream>
2829
#include <memory>
2930
#include <sstream> // IWYU pragma: keep
3031
#include <string>
32+
#include <system_error>
33+
#include <type_traits>
3134
#include <vector>
3235

3336
#include "agg_file.h"
@@ -39,85 +42,103 @@
3942

4043
int main( int argc, char ** argv )
4144
{
42-
if ( argc != 4 ) {
43-
std::cout << argv[0] << " inputFile.icn destinationDirectory paletteFileName.pal" << std::endl;
45+
if ( argc < 4 ) {
46+
std::cerr << argv[0] << " destinationDirectory paletteFileName.pal inputFile.icn ..." << std::endl;
4447
return EXIT_FAILURE;
4548
}
4649

47-
std::string inputFileName( argv[1] );
48-
std::string prefix( argv[2] );
49-
50-
std::string paletteFileName( argv[3] );
50+
const char * dstDir = argv[1];
51+
const char * paletteFileName = argv[2];
5152

5253
StreamFile paletteFile;
5354
if ( !paletteFile.open( paletteFileName, "rb" ) ) {
54-
std::cout << "Cannot open " << paletteFileName << std::endl;
55+
std::cerr << "Cannot open " << paletteFileName << std::endl;
5556
return EXIT_FAILURE;
5657
}
5758

58-
std::vector<uint8_t> palette = paletteFile.getRaw();
59+
const std::vector<uint8_t> palette = paletteFile.getRaw();
5960
if ( palette.size() != 768 ) {
60-
std::cout << "Invalid palette size of " << palette.size() << " instead of 768." << std::endl;
61+
std::cerr << "Invalid palette size of " << palette.size() << " instead of 768." << std::endl;
6162
return EXIT_FAILURE;
6263
}
6364

6465
fheroes2::setGamePalette( palette );
6566

66-
StreamFile sf;
67-
68-
if ( !sf.open( inputFileName, "rb" ) ) {
69-
std::cout << "Cannot open " << inputFileName << std::endl;
70-
return EXIT_FAILURE;
67+
std::vector<std::string> inputFileNames;
68+
for ( int i = 3; i < argc; ++i ) {
69+
if ( System::isShellLevelGlobbingSupported() ) {
70+
inputFileNames.emplace_back( argv[i] );
71+
}
72+
else {
73+
System::globFiles( argv[i], inputFileNames );
74+
}
7175
}
7276

73-
int count_sprite = sf.getLE16();
74-
int total_size = sf.getLE32();
77+
for ( const std::string & inputFileName : inputFileNames ) {
78+
StreamFile sf;
7579

76-
inputFileName.replace( inputFileName.find( ".icn" ), 4, "" );
77-
prefix = System::concatPath( prefix, inputFileName );
80+
if ( !sf.open( inputFileName, "rb" ) ) {
81+
std::cerr << "Cannot open " << inputFileName << std::endl;
82+
return EXIT_FAILURE;
83+
}
7884

79-
if ( 0 != System::MakeDirectory( prefix ) ) {
80-
std::cout << "error mkdir: " << prefix << std::endl;
81-
return EXIT_SUCCESS;
82-
}
85+
const std::filesystem::path prefix = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).stem();
86+
87+
std::error_code ec;
88+
89+
// Using the non-throwing overloads
90+
if ( !std::filesystem::exists( prefix, ec ) && !std::filesystem::create_directories( prefix, ec ) ) {
91+
std::cerr << "Cannot create directory " << prefix << std::endl;
92+
return EXIT_FAILURE;
93+
}
94+
95+
std::cout << std::endl << "Processing " << inputFileName << "..." << std::endl << std::endl;
8396

84-
size_t save_pos = sf.tell();
97+
const uint16_t spritesCount = sf.getLE16();
98+
const uint32_t totalSize = sf.getLE32();
8599

86-
std::vector<fheroes2::ICNHeader> headers( count_sprite );
87-
for ( int ii = 0; ii < count_sprite; ++ii )
88-
sf >> headers[ii];
100+
const size_t beginPos = sf.tell();
89101

90-
for ( int ii = 0; ii < count_sprite; ++ii ) {
91-
const fheroes2::ICNHeader & head = headers[ii];
102+
std::vector<fheroes2::ICNHeader> headers( spritesCount );
103+
for ( fheroes2::ICNHeader & header : headers ) {
104+
sf >> header;
105+
}
92106

93-
uint32_t data_size = ( ii + 1 != count_sprite ? headers[ii + 1].offsetData - head.offsetData : total_size - head.offsetData );
94-
sf.seek( save_pos + head.offsetData );
95-
std::cerr << data_size << std::endl;
96-
std::vector<uint8_t> buf = sf.getRaw( data_size );
107+
for ( uint16_t spriteIdx = 0; spriteIdx < spritesCount; ++spriteIdx ) {
108+
const fheroes2::ICNHeader & header = headers[spriteIdx];
97109

98-
if ( !buf.empty() ) {
99-
const fheroes2::Sprite image = fheroes2::decodeICNSprite( &buf[0], data_size, head.width, head.height, head.offsetX, head.offsetY );
110+
sf.seek( beginPos + header.offsetData );
111+
112+
const uint32_t dataSize = ( spriteIdx + 1 < spritesCount ? headers[spriteIdx + 1].offsetData - header.offsetData : totalSize - header.offsetData );
113+
114+
const std::vector<uint8_t> buf = sf.getRaw( dataSize );
115+
if ( buf.empty() ) {
116+
continue;
117+
}
118+
119+
const fheroes2::Sprite image = fheroes2::decodeICNSprite( buf.data(), dataSize, header.width, header.height, header.offsetX, header.offsetY );
100120

101121
std::ostringstream os;
102-
os << std::setw( 3 ) << std::setfill( '0' ) << ii;
122+
os << std::setw( 3 ) << std::setfill( '0' ) << spriteIdx;
103123

104-
std::string dstfile = System::concatPath( prefix, os.str() );
124+
std::string dstFileName = ( prefix / os.str() ).string();
105125

106126
if ( fheroes2::isPNGFormatSupported() ) {
107-
dstfile += ".png";
127+
dstFileName += ".png";
108128
}
109129
else {
110-
dstfile += ".bmp";
130+
dstFileName += ".bmp";
111131
}
112132

113-
std::cout << "Image " << ii + 1 << " has offset of [" << static_cast<int32_t>( head.offsetX ) << ", " << static_cast<int32_t>( head.offsetY ) << "]"
114-
<< std::endl;
133+
static_assert( std::is_same_v<decltype( header.offsetX ), uint16_t> && std::is_same_v<decltype( header.offsetY ), uint16_t>,
134+
"Offset types have been changed, check the casts below" );
115135

116-
fheroes2::Save( image, dstfile, 23 );
136+
std::cout << "Image " << spriteIdx + 1 << " has offset of [" << static_cast<int16_t>( header.offsetX ) << ", " << static_cast<int16_t>( header.offsetY )
137+
<< "]" << std::endl;
138+
139+
fheroes2::Save( image, dstFileName, 23 );
117140
}
118141
}
119142

120-
sf.close();
121-
122143
return EXIT_SUCCESS;
123144
}

0 commit comments

Comments
 (0)