mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-22 19:29:18 +01:00
e8f5ab07bd
* Fixed bug in http.c (where realloc *might* choose another address, thanks Dr. Clipper) * Added support to /wbfs/Game Title [GAMEID].wbfs files (thanks oggzee) * Fixed bug in cfg_cleanup when switching partitions * Added BGM class again (for playing background music, requested by dimok) * Added settings for background music again * Fixed bug in MEM2 class (returning an invalid handle when no memory could be allocated, thanks to dimok) * Updated DIP module to OpenDIP (report bugs with this one if you've found them, but report them here: http://github.com/spacemanspiff/odip-plugin) * Added initial code for cios 222 rev5 (THIS DOES NOT WORK YET! DON'T FILE BUGS ON THIS ONE!) * Added fatffs module by Hermes and Waninkoko (THIS DOES NOT WORK YET! DON'T FILE BUGS ON THIS ONE!) * Fixed bug in Settings, which resulted in a crash when the partition was changed. * Added caching for gamelist entries, so switching between different screens/sort options/display modes should be faster * Changed defines in ehc_module, in order to prevent clashes with new defines in rev5
538 lines
16 KiB
C++
538 lines
16 KiB
C++
/****************************************************************************
|
|
* libwiigui Template
|
|
* Tantric 2009
|
|
*
|
|
* modified by dimok
|
|
*
|
|
* filebrowser.cpp
|
|
*
|
|
* Generic file routines - reading, writing, browsing
|
|
***************************************************************************/
|
|
|
|
#include <gccore.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wiiuse/wpad.h>
|
|
#include <sys/dir.h>
|
|
#include <sys/iosupport.h>
|
|
#include <malloc.h>
|
|
#include <algorithm>
|
|
|
|
#include "menu.h"
|
|
|
|
#include "listfiles.h"
|
|
#include "language/gettext.h"
|
|
#include "PromptWindows.h"
|
|
#include "libwiigui/gui.h"
|
|
#include "sys.h"
|
|
#include "filebrowser.h"
|
|
|
|
/*** Extern variables ***/
|
|
extern GuiWindow * mainWindow;
|
|
extern u8 shutdown;
|
|
extern u8 reset;
|
|
|
|
/*** Extern functions ***/
|
|
extern void ResumeGui();
|
|
extern void HaltGui();
|
|
|
|
static int curDevice = -1;
|
|
static std::vector<BROWSERINFO> browsers;
|
|
BROWSERINFO *browser = NULL;
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
* FileFilterCallbacks
|
|
* return: 1-visible 0-hidden
|
|
***************************************************************************/
|
|
int noDIRS(BROWSERENTRY *Entry, void* Args)
|
|
{
|
|
return !Entry->isdir;
|
|
}
|
|
int noFILES(BROWSERENTRY *Entry, void* Args)
|
|
{
|
|
return Entry->isdir;
|
|
}
|
|
int noEXT(BROWSERENTRY *Entry, void* Args)
|
|
{
|
|
if(!Entry->isdir)
|
|
{
|
|
char *cptr = strrchr(Entry->displayname, '.');
|
|
if(cptr && cptr!= Entry->displayname) *cptr = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void ResetBrowser(BROWSERINFO *browser);
|
|
/****************************************************************************
|
|
* InitBrowsers()
|
|
* Clears the file browser memory, and allocates one initial entry
|
|
***************************************************************************/
|
|
int InitBrowsers() {
|
|
curDevice = -1;
|
|
browsers.clear();
|
|
browser = NULL;
|
|
char rootdir[ROOTDIRLEN];
|
|
for(int i=3; i<STD_MAX; i++)
|
|
{
|
|
if(strcmp(devoptab_list[i]->name, "stdnull") && devoptab_list[i]->write_r != NULL)
|
|
{
|
|
snprintf(rootdir, sizeof(rootdir) , "%s:/", devoptab_list[i]->name);
|
|
if(DIR_ITER *dir = diropen(rootdir))
|
|
{
|
|
dirclose(dir);
|
|
BROWSERINFO browser;
|
|
browser.dir[0] = '\0';
|
|
strcpy(browser.rootdir, rootdir);
|
|
ResetBrowser(&browser);
|
|
browsers.push_back(browser);
|
|
}
|
|
}
|
|
}
|
|
if(!browsers.size()) return -1;
|
|
curDevice = 0;
|
|
browser = &browsers[curDevice];
|
|
return 0;
|
|
}
|
|
/****************************************************************************
|
|
* ResetBrowser()
|
|
* Clears the file browser memory, and allocates one initial entry
|
|
***************************************************************************/
|
|
void ResetBrowser(BROWSERINFO *browser) {
|
|
browser->pageIndex = 0;
|
|
browser->browserList.clear();
|
|
/*
|
|
// Clear any existing values
|
|
if (browser->browserList != NULL) {
|
|
free(browser->browserList);
|
|
browser->browserList = NULL;
|
|
}
|
|
// set aside space for 1 entry
|
|
browser->browserList = (BROWSERENTRY *)malloc(sizeof(BROWSERENTRY));
|
|
if(browser->browserList)
|
|
memset(browser->browserList, 0, sizeof(BROWSERENTRY));
|
|
*/
|
|
}
|
|
|
|
/****************************************************************************
|
|
* FileSortCallback
|
|
*
|
|
* sort callback to sort file entries with the following order:
|
|
* .
|
|
* ..
|
|
* <dirs>
|
|
* <files>
|
|
***************************************************************************/
|
|
//int FileSortCallback(const void *f1, const void *f2) {
|
|
bool operator< (const BROWSERENTRY &f1, const BROWSERENTRY &f2) {
|
|
/* Special case for implicit directories */
|
|
if (f1.filename[0] == '.' || f2.filename[0] == '.') {
|
|
if (strcmp(f1.filename, ".") == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp(f2.filename, ".") == 0) {
|
|
return false;
|
|
}
|
|
if (strcmp(f1.filename, "..") == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp(f2.filename, "..") == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* If one is a file and one is a directory the directory is first. */
|
|
if (f1.isdir && !(f2.isdir)) return true;
|
|
if (!(f1.isdir) && f2.isdir) return false;
|
|
|
|
return stricmp(f1.filename, f2.filename)<0;
|
|
}
|
|
|
|
int ParseFilter(FILTERCASCADE *Filter, BROWSERENTRY* Entry)
|
|
{
|
|
while(Filter)
|
|
{
|
|
if(Filter->filter && Filter->filter(Entry, Filter->filter_args) == 0)
|
|
return 0;
|
|
Filter = Filter->next;
|
|
}
|
|
return 1;
|
|
}
|
|
/***************************************************************************
|
|
* Browse subdirectories
|
|
**************************************************************************/
|
|
int ParseDirectory(const char* Path, int Flags, FILTERCASCADE *Filter) {
|
|
DIR_ITER *dir = NULL;
|
|
char fulldir[MAXPATHLEN];
|
|
char filename[MAXPATHLEN];
|
|
struct stat filestat;
|
|
unsigned int i;
|
|
|
|
if(curDevice == -1)
|
|
if(InitBrowsers()) return -1; // InitBrowser fails
|
|
|
|
if(Path) // note in this codeblock use filename temporary
|
|
{
|
|
strlcpy(fulldir, Path, sizeof(fulldir));
|
|
if(*fulldir && fulldir[strlen(fulldir)-1] != '/') // a file
|
|
{
|
|
char * chrp = strrchr(fulldir, '/');
|
|
if(chrp) chrp[1] = 0;
|
|
}
|
|
if(strchr(fulldir, ':') == NULL) // Path has no device device
|
|
{
|
|
getcwd(filename, sizeof(filename)); // save the current working dir
|
|
if(*fulldir==0) // if path is empty
|
|
strlcpy(fulldir, filename, sizeof(fulldir)); // we use the current working dir
|
|
else
|
|
{ // path is not empty
|
|
if(chdir(fulldir)); // sets the path to concatenate and validate
|
|
{
|
|
if(Flags & (FB_TRYROOTDIR | FB_TRYSTDDEV))
|
|
if(chdir("/") && !(Flags & FB_TRYSTDDEV))// try to set root if is needed
|
|
return -1;
|
|
}
|
|
if(getcwd(fulldir, sizeof(fulldir))) return -1; // gets the concatenated current working dir
|
|
chdir(filename); // restore the saved cwd
|
|
}
|
|
}
|
|
for(i=0; i<browsers.size(); i++) // searchs the browser who match the path
|
|
{
|
|
if(strnicmp(fulldir, browsers[i].rootdir, strlen(browsers[i].rootdir)-1 /*means without trailing '/'*/) == 0)
|
|
{
|
|
browser = &browsers[curDevice];
|
|
break;
|
|
}
|
|
}
|
|
if(i != browsers.size()) // found browser
|
|
{
|
|
curDevice = i;
|
|
browser = &browsers[curDevice];
|
|
strcpy(browser->dir, &fulldir[strlen(browser->rootdir)]);
|
|
}
|
|
else if(Flags & FB_TRYSTDDEV)
|
|
{
|
|
curDevice = 0;
|
|
browser = &browsers[curDevice]; // when no browser was found and
|
|
browser->dir[0] = 0; // we alowed try StdDevice and try RootDir
|
|
strlcpy(fulldir, browser->rootdir, sizeof(fulldir)); // set the first browser with root-dir
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
else
|
|
snprintf(fulldir, sizeof(fulldir), "%s%s", browser->rootdir, browser->dir);
|
|
|
|
// reset browser
|
|
ResetBrowser(browser);
|
|
|
|
// open the directory
|
|
if((dir = diropen(fulldir)) == NULL)
|
|
{
|
|
if(Flags & FB_TRYROOTDIR)
|
|
{
|
|
browser->dir[0] = 0;
|
|
if((dir = diropen(browser->rootdir)) == NULL)
|
|
return -1;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
while (dirnext(dir,filename,&filestat) == 0)
|
|
{
|
|
if (strcmp(filename,".") != 0)
|
|
{
|
|
BROWSERENTRY newEntry;
|
|
memset(&newEntry, 0, sizeof(BROWSERENTRY)); // clear the new entry
|
|
strlcpy(newEntry.filename, filename, sizeof(newEntry.filename));
|
|
strlcpy(newEntry.displayname, filename, sizeof(newEntry.displayname));
|
|
newEntry.length = filestat.st_size;
|
|
newEntry.isdir = (filestat.st_mode & S_IFDIR) == 0 ? 0 : 1; // flag this as a dir
|
|
if(ParseFilter(Filter, &newEntry))
|
|
browser->browserList.push_back(newEntry);
|
|
}
|
|
}
|
|
|
|
// close directory
|
|
dirclose(dir);
|
|
|
|
// Sort the file list
|
|
std::sort(browser->browserList.begin(), browser->browserList.end());
|
|
return 0;
|
|
}
|
|
int ParseDirectory(int Device, int Flags, FILTERCASCADE *Filter)
|
|
{
|
|
if(Device >=0 && Device < (int)browsers.size())
|
|
{
|
|
int old_curDevice = curDevice;
|
|
curDevice = Device;
|
|
browser = &browsers[curDevice];
|
|
if(ParseDirectory((char*)NULL, Flags, Filter) == 0) return 0;
|
|
curDevice = old_curDevice;
|
|
browser = &browsers[old_curDevice];
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* BrowseDevice
|
|
* Displays a list of files on the selected path
|
|
***************************************************************************/
|
|
|
|
int BrowseDevice(char * Path, int Path_size, int Flags, FILTERCASCADE *Filter/*=NULL*/)
|
|
{
|
|
int result=-1;
|
|
int i;
|
|
|
|
if(InitBrowsers() || ParseDirectory(Path, Flags, Filter))
|
|
{
|
|
WindowPrompt(tr("Error"),0,tr("OK"));
|
|
return -1;
|
|
}
|
|
int menu = MENU_NONE;
|
|
|
|
/*
|
|
GuiText titleTxt("Browse Files", 28, (GXColor){0, 0, 0, 230});
|
|
titleTxt.SetAlignment(ALIGN_LEFT, ALIGN_TOP);
|
|
titleTxt.SetPosition(70,20);
|
|
*/
|
|
GuiTrigger trigA;
|
|
trigA.SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A);
|
|
GuiTrigger trigB;
|
|
trigB.SetButtonOnlyTrigger(-1, WPAD_BUTTON_B | WPAD_CLASSIC_BUTTON_B, PAD_BUTTON_B);
|
|
|
|
GuiSound btnSoundOver(button_over_pcm, button_over_pcm_size, Settings.sfxvolume);
|
|
// because destroy GuiSound must wait while sound playing is finished, we use a global sound
|
|
if(!btnClick2) btnClick2=new GuiSound(button_click2_pcm, button_click2_pcm_size, Settings.sfxvolume);
|
|
// GuiSound btnClick(button_click2_pcm, button_click2_pcm_size, Settings.sfxvolume);
|
|
|
|
GuiImageData folderImgData(icon_folder_png);
|
|
GuiImage folderImg(&folderImgData);
|
|
GuiButton folderBtn(folderImg.GetWidth(), folderImg.GetHeight());
|
|
folderBtn.SetAlignment(ALIGN_CENTRE, ALIGN_MIDDLE);
|
|
folderBtn.SetPosition(-210, -145);
|
|
folderBtn.SetImage(&folderImg);
|
|
folderBtn.SetTrigger(&trigA);
|
|
folderBtn.SetEffectGrow();
|
|
|
|
char imgPath[100];
|
|
snprintf(imgPath, sizeof(imgPath), "%sbutton_dialogue_box.png", CFG.theme_path);
|
|
GuiImageData btnOutline(imgPath, button_dialogue_box_png);
|
|
GuiText ExitBtnTxt(tr("Cancel"), 24, (GXColor) {0, 0, 0, 255});
|
|
GuiImage ExitBtnImg(&btnOutline);
|
|
if (Settings.wsprompt == yes) {
|
|
ExitBtnTxt.SetWidescreen(CFG.widescreen);
|
|
ExitBtnImg.SetWidescreen(CFG.widescreen);
|
|
}
|
|
GuiButton ExitBtn(btnOutline.GetWidth(), btnOutline.GetHeight());
|
|
ExitBtn.SetAlignment(ALIGN_RIGHT, ALIGN_BOTTOM);
|
|
ExitBtn.SetPosition(-40, -35);
|
|
ExitBtn.SetLabel(&ExitBtnTxt);
|
|
ExitBtn.SetImage(&ExitBtnImg);
|
|
ExitBtn.SetTrigger(&trigA);
|
|
ExitBtn.SetTrigger(&trigB);
|
|
ExitBtn.SetEffectGrow();
|
|
|
|
GuiText usbBtnTxt(browsers[(curDevice+1)%browsers.size()].rootdir, 24, (GXColor) {0, 0, 0, 255});
|
|
GuiImage usbBtnImg(&btnOutline);
|
|
if (Settings.wsprompt == yes) {
|
|
usbBtnTxt.SetWidescreen(CFG.widescreen);
|
|
usbBtnImg.SetWidescreen(CFG.widescreen);
|
|
}
|
|
GuiButton usbBtn(btnOutline.GetWidth(), btnOutline.GetHeight());
|
|
usbBtn.SetAlignment(ALIGN_CENTRE, ALIGN_BOTTOM);
|
|
usbBtn.SetPosition(0, -35);
|
|
usbBtn.SetLabel(&usbBtnTxt);
|
|
usbBtn.SetImage(&usbBtnImg);
|
|
usbBtn.SetTrigger(&trigA);
|
|
usbBtn.SetEffectGrow();
|
|
|
|
GuiText okBtnTxt(tr("OK"), 22, THEME.prompttext);
|
|
GuiImage okBtnImg(&btnOutline);
|
|
if (Settings.wsprompt == yes) {
|
|
okBtnTxt.SetWidescreen(CFG.widescreen);
|
|
okBtnImg.SetWidescreen(CFG.widescreen);
|
|
}
|
|
GuiButton okBtn(&okBtnImg,&okBtnImg, 0, 4, 40, -35, &trigA, &btnSoundOver, btnClick2,1);
|
|
okBtn.SetLabel(&okBtnTxt);
|
|
|
|
GuiFileBrowser fileBrowser(396, 248);
|
|
fileBrowser.SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
|
|
fileBrowser.SetPosition(0, 120);
|
|
|
|
GuiImageData Address(addressbar_textbox_png);
|
|
GuiText AdressText(NULL, 20, (GXColor) { 0, 0, 0, 255});
|
|
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
|
AdressText.SetAlignment(ALIGN_LEFT, ALIGN_MIDDLE);
|
|
AdressText.SetPosition(20, 0);
|
|
AdressText.SetMaxWidth(Address.GetWidth()-40, GuiText::SCROLL);
|
|
GuiImage AdressbarImg(&Address);
|
|
GuiButton Adressbar(Address.GetWidth(), Address.GetHeight());
|
|
Adressbar.SetAlignment(ALIGN_CENTRE, ALIGN_TOP);
|
|
Adressbar.SetPosition(0, fileBrowser.GetTop()-45);
|
|
Adressbar.SetImage(&AdressbarImg);
|
|
Adressbar.SetLabel(&AdressText);
|
|
|
|
HaltGui();
|
|
GuiWindow w(screenwidth, screenheight);
|
|
w.Append(&ExitBtn);
|
|
// w.Append(&titleTxt);
|
|
w.Append(&fileBrowser);
|
|
w.Append(&Adressbar);
|
|
w.Append(&okBtn);
|
|
if(!(Flags & FB_NOFOLDER_BTN))
|
|
w.Append(&folderBtn);
|
|
if(browsers.size()>1 && !(Flags & FB_NODEVICE_BTN))
|
|
w.Append(&usbBtn);
|
|
mainWindow->Append(&w);
|
|
ResumeGui();
|
|
int clickedIndex = -1;
|
|
while (menu == MENU_NONE) {
|
|
VIDEO_WaitVSync();
|
|
|
|
if (shutdown == 1)
|
|
Sys_Shutdown();
|
|
|
|
if (reset == 1)
|
|
Sys_Reboot();
|
|
|
|
for (i=0; i<FILEBROWSERSIZE; i++) {
|
|
if (fileBrowser.fileList[i]->GetState() == STATE_CLICKED) {
|
|
fileBrowser.fileList[i]->ResetState();
|
|
|
|
clickedIndex = browser->pageIndex + i;
|
|
bool pathCanged = false;
|
|
// check corresponding browser entry
|
|
if (browser->browserList[clickedIndex].isdir)
|
|
{
|
|
/* go up to parent directory */
|
|
if (strcmp(browser->browserList[clickedIndex].filename,"..") == 0)
|
|
{
|
|
/* remove last subdirectory name */
|
|
int len = strlen(browser->dir);
|
|
while(browser->dir[0] && browser->dir[len-1] == '/')
|
|
browser->dir[--len] = '\0'; // remove all trailing '/'
|
|
char *cptr = strrchr(browser->dir, '/');
|
|
if(cptr) *++cptr = 0; else browser->dir[0] = '\0'; // remove trailing dir
|
|
pathCanged = true;
|
|
}
|
|
/* Open a directory */
|
|
/* current directory doesn't change */
|
|
else if (strcmp(browser->browserList[clickedIndex].filename,"."))
|
|
{
|
|
/* test new directory namelength */
|
|
if ((strlen(browser->dir) + strlen(browser->browserList[clickedIndex].filename)
|
|
+ 1/*'/'*/) < MAXPATHLEN)
|
|
{
|
|
/* update current directory name */
|
|
sprintf(browser->dir, "%s%s/",browser->dir,
|
|
browser->browserList[clickedIndex].filename);
|
|
pathCanged = true;
|
|
}
|
|
}
|
|
if (pathCanged)
|
|
{
|
|
LOCK(&fileBrowser);
|
|
ParseDirectory((char*)NULL, Flags, Filter);
|
|
fileBrowser.ResetState();
|
|
fileBrowser.TriggerUpdate();
|
|
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
|
}
|
|
clickedIndex = -1;
|
|
}
|
|
else /* isFile */
|
|
{
|
|
AdressText.SetTextf("%s%s%s", browser->rootdir, browser->dir, browser->browserList[clickedIndex].filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ExitBtn.GetState() == STATE_CLICKED) {
|
|
result = 0;
|
|
break;
|
|
}
|
|
else if (okBtn.GetState() == STATE_CLICKED) {
|
|
if(clickedIndex>=0)
|
|
snprintf(Path, Path_size, "%s%s%s", browser->rootdir, browser->dir,browser->browserList[clickedIndex].filename);
|
|
else
|
|
snprintf(Path, Path_size, "%s%s", browser->rootdir, browser->dir);
|
|
result = 1;
|
|
break;
|
|
}
|
|
else if (usbBtn.GetState() == STATE_CLICKED) {
|
|
usbBtn.ResetState();
|
|
for(u32 i=1; i<browsers.size(); i++)
|
|
{
|
|
LOCK(&fileBrowser);
|
|
if(ParseDirectory((curDevice+i) % browsers.size(), Flags, Filter)==0)
|
|
{
|
|
fileBrowser.ResetState();
|
|
fileBrowser.TriggerUpdate();
|
|
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
|
usbBtnTxt.SetText(browsers[(curDevice+1)%browsers.size()].rootdir);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (folderBtn.GetState() == STATE_CLICKED) {
|
|
folderBtn.ResetState();
|
|
|
|
HaltGui();
|
|
mainWindow->Remove(&w);
|
|
ResumeGui();
|
|
char newfolder[100];
|
|
char oldfolder[100];
|
|
snprintf(newfolder, sizeof(newfolder), "%s%s", browser->rootdir, browser->dir);
|
|
strcpy(oldfolder,newfolder);
|
|
|
|
int result = OnScreenKeyboard(newfolder, sizeof(newfolder), strlen(browser->rootdir));
|
|
if ( result == 1 ) {
|
|
unsigned int len = strlen(newfolder);
|
|
if (len>0 && len+1 < sizeof(newfolder) && newfolder[len-1] !='/')
|
|
{
|
|
newfolder[len] = '/';
|
|
newfolder[len+1] = '\0';
|
|
}
|
|
|
|
struct stat st;
|
|
if (stat(newfolder, &st) != 0) {
|
|
if(WindowPrompt(tr("Directory does not exist!"),tr("The entered directory does not exist. Would you like to create it?") ,tr("OK"), tr("Cancel")) == 1)
|
|
if (subfoldercreate(newfolder) == false)
|
|
WindowPrompt(tr("Error !"),tr("Can't create directory"),tr("OK"));
|
|
}
|
|
if(ParseDirectory(newfolder, Flags, Filter)==0)
|
|
{
|
|
fileBrowser.ResetState();
|
|
fileBrowser.TriggerUpdate();
|
|
AdressText.SetTextf("%s%s", browser->rootdir, browser->dir);
|
|
usbBtnTxt.SetText(browsers[(curDevice+1)%browsers.size()].rootdir);
|
|
}
|
|
}
|
|
HaltGui();
|
|
mainWindow->Append(&w);
|
|
ResumeGui();
|
|
}
|
|
|
|
}
|
|
HaltGui();
|
|
mainWindow->Remove(&w);
|
|
ResumeGui();
|
|
|
|
//}
|
|
|
|
return result;
|
|
}
|
|
|
|
int BrowseDevice(char * Path, int Path_size, int Flags, FILEFILTERCALLBACK Filter, void *FilterArgs)
|
|
{
|
|
if(Filter)
|
|
{
|
|
FILTERCASCADE filter = {Filter, FilterArgs, NULL};
|
|
return BrowseDevice(Path, Path_size, Flags, &filter);
|
|
}
|
|
return BrowseDevice(Path, Path_size, Flags);
|
|
}
|