mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
544 lines
17 KiB
544 lines
17 KiB
//*********************************************************************
|
|
//* Microsoft Windows **
|
|
//* Copyright(c) Microsoft Corp., 1995 **
|
|
//*********************************************************************
|
|
|
|
/*Included Files------------------------------------------------------------*/
|
|
#include "all.h"
|
|
#include "safestrm.h"
|
|
#include "decoder.h"
|
|
#include "history.h"
|
|
#include "midi.h"
|
|
#include "mci.h"
|
|
#include "wc_html.h"
|
|
#include "blob.h"
|
|
|
|
#ifdef FEATURE_VRML
|
|
#include "vrml.h"
|
|
#endif
|
|
|
|
|
|
/*Structures----------------------------------------------------------------*/
|
|
|
|
/*
|
|
This is the internal structure that will eventually get passed
|
|
to the blob callback
|
|
*/
|
|
|
|
PBGBLOBPARAMS BGBLOBPARAMSConstruct(){
|
|
PBGBLOBPARAMS pBGBlobParams;
|
|
|
|
pBGBlobParams = GTR_MALLOC(sizeof(*pBGBlobParams));
|
|
if (pBGBlobParams){
|
|
memset(pBGBlobParams, 0, sizeof(*pBGBlobParams));
|
|
}
|
|
return pBGBlobParams;
|
|
}
|
|
|
|
static void BGBLOBPARAMSDestruct(PBGBLOBPARAMS pBGBlobParams){
|
|
/*nore any of this*/
|
|
if (pBGBlobParams) {
|
|
if (pBGBlobParams->pszFilePath) {
|
|
GTR_FREE(pBGBlobParams->pszFilePath);
|
|
}
|
|
if (pBGBlobParams->szRequestedURL) {
|
|
GTR_FREE(pBGBlobParams->szRequestedURL);
|
|
}
|
|
GTR_FREE(pBGBlobParams);
|
|
}
|
|
}
|
|
|
|
static void BackgroundBlob_Callback(void *param, const char *pszURL, BOOL bAbort, const char *pszFileHREF, BOOL fDCache, DCACHETIME dctExpires, DCACHETIME dctLastModif)
|
|
{
|
|
BGBLOBPARAMS * pBGBlobParams = (BGBLOBPARAMS *) param;
|
|
ELEMENT *pel;
|
|
int z;
|
|
BOOL fResetDCacheCurDoc=FALSE;
|
|
|
|
/*Did everything go as planned?*/
|
|
if (!bAbort && pBGBlobParams && pBGBlobParams->pszFilePath) {
|
|
/*Cache this file*/
|
|
if (fDCache && gPrefs.bEnableDiskCache) {
|
|
FUpdateBuiltinDCache(pBGBlobParams->OriginalFormat,
|
|
pszURL, &pBGBlobParams->pszFilePath,
|
|
dctExpires, dctLastModif, TRUE, pBGBlobParams->tw);
|
|
fResetDCacheCurDoc=TRUE;
|
|
}
|
|
|
|
/*Start everyone that requested a fixup*/
|
|
if (pBGBlobParams->tw && pBGBlobParams->tw->w3doc){
|
|
pel = pBGBlobParams->tw->w3doc->aElements;
|
|
for (z=0;z>=0;z=pel[z].next){
|
|
if ( (pel[z].pblob)
|
|
&& (pel[z].pblob->dwFlags & BLOB_FLAGS_FIXUP)
|
|
&& (0 == _stricmp(pel[z].pblob->szURL, pel[pBGBlobParams->iIndex].pblob->szURL))
|
|
){
|
|
ASSERT(!(pel[z].pblob->dwFlags & BLOB_FLAGS_LOADED));
|
|
/*reset state flags*/
|
|
pel[z].pblob->dwFlags &= ~(BLOB_FLAGS_LOADING|BLOB_FLAGS_FIXUP);
|
|
pel[z].pblob->dwFlags |= BLOB_FLAGS_LOADED;
|
|
/*fill in blob structure with newly resolved local file name*/
|
|
if (BlobStoreFileName(pel[z].pblob, (char*) pBGBlobParams->pszFilePath)){
|
|
/*callback to start execution*/
|
|
(*pBGBlobParams->pCallback)(pBGBlobParams->tw, pel+z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fResetDCacheCurDoc)
|
|
ResetCIFEntryCurDoc(pszURL);
|
|
|
|
/*clean up callback data*/
|
|
BGBLOBPARAMSDestruct(pBGBlobParams);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Called from within Image_LoadAll_Async in imgcache.c, this lightweight
|
|
thread is responsible for loading the pel/blob combo that was passed in.
|
|
*/
|
|
#define STATE_BLOB_LOADED STATE_OTHER
|
|
|
|
int LoadBackgroundBlobs_Async(struct Mwin *tw, int nState, void **ppInfo){
|
|
struct Params_LoadBackgroundBlobs *pParams;
|
|
struct Params_LoadAsync *pLoadParams;
|
|
PBGBLOBPARAMS pBGBlobParams;
|
|
ELEMENT *pel;
|
|
char *pFileName;
|
|
BOOL fWire;
|
|
int output_format;
|
|
int z;
|
|
int iStatusBarTextID;
|
|
BOOL bAutoLoadVideos = gPrefs.bAutoLoadVideos;
|
|
|
|
if (ppInfo){
|
|
pParams = *ppInfo;
|
|
switch (nState) {
|
|
case STATE_INIT:
|
|
if (!tw || !tw->w3doc) return STATE_ABORT;
|
|
|
|
pel = pParams->twDoc->w3doc->aElements;
|
|
|
|
#ifdef FEATURE_VRML
|
|
// If we're on a full pane VRML page, pretend gprefs.bAutoLoadVideos is TRUE
|
|
if ( (tw->w3doc->flags & W3DOC_FLAG_FULL_PANE_VRML) &&
|
|
(pel[pParams->iIndex].lFlags & ELEFLAG_FULL_PANE_VRML_FIRST_LOAD ) &&
|
|
!(pel[pParams->iIndex].lFlags & ELEFLAG_HIDDEN )
|
|
)
|
|
{
|
|
// Clear the first load flag, so that reconnects to page will load
|
|
pel[pParams->iIndex].lFlags &= ~ELEFLAG_FULL_PANE_VRML_FIRST_LOAD;
|
|
bAutoLoadVideos = TRUE;
|
|
// we bail out since we're downloading it from another place
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
#endif
|
|
/*Is another instance of us loading?*/
|
|
ASSERT(pel[pParams->iIndex].pblob);
|
|
for (z=0;z>=0;z=pel[z].next){
|
|
if ((z!=pParams->iIndex)
|
|
&& (pel[z].pblob)
|
|
&& (0 == _stricmp(pel[z].pblob->szURL, pel[pParams->iIndex].pblob->szURL))
|
|
){
|
|
if (pel[z].pblob->dwFlags & BLOB_FLAGS_LOADING){
|
|
pel[pParams->iIndex].pblob->dwFlags |= BLOB_FLAGS_FIXUP;
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
pel = pParams->twDoc->w3doc->aElements + pParams->iIndex;
|
|
|
|
// making the assertion that this loop above is going
|
|
// to find a pel with a pblob
|
|
ASSERT(pel);
|
|
ASSERT(pel->pblob);
|
|
|
|
// clear any error that may be set
|
|
pel->pblob->dwFlags &= ~BLOB_FLAGS_ERROR;
|
|
|
|
/*do we get this from the wire, or is the local version ok?!?!?*/
|
|
if (PROT_FILE == ProtocolIdentify(pel->pblob->szURL))
|
|
{
|
|
fWire = FALSE;
|
|
pFileName = pel->pblob->szURL+5;
|
|
}
|
|
else {
|
|
fWire = TRUE;
|
|
pFileName = NULL;
|
|
}
|
|
if (pFileName && !BlobStoreFileName(pel->pblob, (char*) pFileName)){
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
|
|
if (pParams->bLocalOnly){
|
|
|
|
// if our autoload images are off, but our play bk sounds
|
|
// are on, then we need to do a download for the sounds.
|
|
// otherwise we'll end up not loading sounds just because
|
|
// we're not showing images, B#391
|
|
|
|
if ( !gPrefs.bAutoLoadImages && gPrefs.bPlayBackgroundSounds &&
|
|
pel->type == ELE_BGSOUND )
|
|
{
|
|
pParams->bLocalOnly = FALSE;
|
|
} else if ( !gPrefs.bAutoLoadImages && bAutoLoadVideos &&
|
|
pel->type == ELE_IMAGE )
|
|
{
|
|
pParams->bLocalOnly = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// otherwise do the normal stuff for bLocalOnly
|
|
if (!pFileName) goto LoadBackgroundBlobs_Async_Exit;
|
|
else fWire = FALSE;
|
|
}
|
|
}
|
|
|
|
// default
|
|
iStatusBarTextID = RES_STRING_BLOB_MCI;
|
|
|
|
/*determine what we are getting anyway. restart if local version ok*/
|
|
switch(pel->type){
|
|
case ELE_BGSOUND:
|
|
iStatusBarTextID = RES_STRING_BLOB_BGSOUND;
|
|
if (gPrefs.bPlayBackgroundSounds){
|
|
|
|
// if we don't have a sound card
|
|
// or MIDI device, then there is no need
|
|
// to download this file, B#539
|
|
if ( waveOutGetNumDevs() == 0 &&
|
|
midiOutGetNumDevs() == 0 )
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
|
|
if (!fWire){
|
|
pel->pblob->dwFlags |= BLOB_FLAGS_LOADED;
|
|
BackgroundSoundFile_Callback(pParams->twDoc, pel);
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
else{
|
|
/* This is a hack to figure out if we have a
|
|
* valid sound file. We basically just check for
|
|
* the filename (url in this case) to have the
|
|
* correct extension. Refer to DwValidSoundFile
|
|
* for details. (Bug 318)
|
|
*/
|
|
if (DwValidSoundFile(pel->pblob->szURL))
|
|
output_format = WWW_BGSOUND;
|
|
else goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
}
|
|
else goto LoadBackgroundBlobs_Async_Exit;
|
|
break;
|
|
case ELE_IMAGE:
|
|
#ifdef FEATURE_VRML
|
|
|
|
ASSERT(pel->pmo || pel->pVrml);
|
|
// if we're forcing a download then we need
|
|
// to download this guy even though we're not
|
|
// supposed to show the images
|
|
|
|
if (pel->pVrml)
|
|
iStatusBarTextID = RES_STRING_BLOB_VRML;
|
|
else
|
|
iStatusBarTextID = RES_STRING_BLOB_MCI;
|
|
|
|
if (bAutoLoadVideos || pParams->bJustOne)
|
|
{
|
|
if (!fWire)
|
|
{
|
|
pel->pblob->dwFlags |= BLOB_FLAGS_LOADED;
|
|
|
|
if (pel->pmo)
|
|
BackgroundMciFile_Callback(pParams->twDoc, pel);
|
|
else if (pel->pVrml)
|
|
BackgroundVRMLFile_Callback(pParams->twDoc, pel);
|
|
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
else
|
|
{
|
|
if (pel->pmo)
|
|
output_format = WWW_MCI;
|
|
else if (pel->pVrml)
|
|
output_format = WWW_VRML;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if we're not in an image download mode
|
|
// then lets stop this download process.
|
|
|
|
if (!gPrefs.bAutoLoadImages && !bAutoLoadVideos && pel->myImage)
|
|
{
|
|
pel->myImage->flags = IMG_NOTLOADED;
|
|
pel->myImage->flags |= IMG_LOADSUP;
|
|
pel->myImage->height = pel->myImage->width = 0;
|
|
}
|
|
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
break;
|
|
|
|
#else
|
|
ASSERT(pel->pmo);
|
|
if (bAutoLoadVideos || pParams->bJustOne){
|
|
if (!fWire){
|
|
pel->pblob->dwFlags |= BLOB_FLAGS_LOADED;
|
|
BackgroundMciFile_Callback(pParams->twDoc, pel);
|
|
goto LoadBackgroundBlobs_Async_Exit;
|
|
}
|
|
else output_format = WWW_MCI;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
ASSERT(0);
|
|
break;
|
|
}
|
|
/*we have a valid thing we are grabbing from the wire*/
|
|
pParams->pDest = Dest_CreateDest(pel->pblob->szURL);
|
|
if (NULL == pParams->pDest) return STATE_ABORT;
|
|
|
|
pLoadParams = GTR_MALLOC(sizeof(*pLoadParams));
|
|
if (pLoadParams == NULL) return STATE_ABORT;
|
|
memset(pLoadParams,0,sizeof(*pLoadParams));
|
|
|
|
pParams->status = 0;
|
|
pLoadParams->pStatus = &(pParams->status);
|
|
// By default, we say that it's not ok to load from dcache.
|
|
// This will kick in the If-Modified-since code if this
|
|
// blob is in dcache. Else it will fetch from wire anyway.
|
|
pLoadParams->fLoadFromDCacheOK = FALSE;
|
|
|
|
/* Set up the request structure */
|
|
pParams->pRequest = pLoadParams->request = HTRequest_new();
|
|
if (pLoadParams->request) {
|
|
/*force the output format so that we get called back*/
|
|
HTFormatInit(pLoadParams->request->conversions);
|
|
pLoadParams->request->output_format = output_format;
|
|
/*create our user defined data and place it in a spot that will be passsed along*/
|
|
pBGBlobParams = BGBLOBPARAMSConstruct();
|
|
if (pBGBlobParams){
|
|
char szBuf[MAX_URL_STRING+1]; // scratch buffer for rendering Status Bar
|
|
char *pszMallocBuf;
|
|
|
|
pBGBlobParams->iIndex = pParams->iIndex;
|
|
pBGBlobParams->tw = pParams->twDoc;
|
|
pLoadParams->request->context = (void *) pBGBlobParams;
|
|
pLoadParams->request->destination = pParams->pDest;
|
|
pLoadParams->request->referer = pParams->twDoc->w3doc->szActualURL;
|
|
pel->pblob->dwFlags |= (BLOB_FLAGS_LOADING|BLOB_FLAGS_FIXUP);
|
|
// if we're downloading a bunch of blobs ie we're doing
|
|
// a complete download of a page, then don't complain on
|
|
// each and every failure. Just show the error placeholder
|
|
if ( ! pParams->bJustOne )
|
|
pLoadParams->request->iFlags |= HTREQ_STOP_WHINING;
|
|
pLoadParams->request->fNotFromCache = pParams->bNoImageCache;
|
|
pLoadParams->request->iFlags &= ~HTREQ_RECORD;
|
|
pLoadParams->request->iFlags |= HTREQ_IF_IN_CACHE_DONT_READ_DATA;
|
|
|
|
pszMallocBuf = GTR_MALLOC( MAX_URL_STRING );
|
|
if ( pszMallocBuf )
|
|
{
|
|
GTR_strncpy(szBuf, pel->pblob->szURL, MAX_URL_STRING );
|
|
make_URL_HumanReadable(szBuf, NULL, FALSE );
|
|
GTR_formatmsg(iStatusBarTextID,pszMallocBuf, MAX_URL_STRING,szBuf);
|
|
WAIT_Push(tw, -1, pszMallocBuf);
|
|
WAIT_Lock( TRUE ) ;
|
|
GTR_FREE(pszMallocBuf);
|
|
}
|
|
else
|
|
{
|
|
// worst case is we won't have our status bar text
|
|
WAIT_Push(tw, -1, NULL);
|
|
}
|
|
|
|
Async_DoCall(HTLoadDocument_Async, pLoadParams);
|
|
return STATE_BLOB_LOADED;
|
|
}
|
|
}
|
|
/*fallthrough to abort*/
|
|
case STATE_ABORT:
|
|
case STATE_BLOB_LOADED:
|
|
if (pParams){
|
|
if (pParams->twDoc && pParams->twDoc->w3doc){
|
|
WAIT_Lock( FALSE ) ;
|
|
WAIT_Pop(tw);
|
|
pel = pParams->twDoc->w3doc->aElements + pParams->iIndex;
|
|
if (pel && pel->pblob)
|
|
{
|
|
pel->pblob->dwFlags &= ~BLOB_FLAGS_LOADING;
|
|
if ( pParams->pRequest && (pParams->pRequest->iFlags & HTREQ_HAD_ERROR))
|
|
{
|
|
pel->pblob->dwFlags |= BLOB_FLAGS_ERROR;
|
|
|
|
#ifdef FEATURE_VRML
|
|
// If we encountered an error when downloading a blob for the VRML hidden
|
|
// element, we need to notify the VRML viewer.
|
|
//
|
|
if (pel->pVrml && (pel->pVrml->dwFlags & VRMLF_INLINE)) {
|
|
BackgroundVRMLFile_Callback(pParams->twDoc, pel);
|
|
}
|
|
#endif
|
|
|
|
// if we don't have an Image, then mark the image as
|
|
// well as errored out
|
|
if ( pel->myImage && !pel->myImage->actualURL)
|
|
pel->myImage->flags = IMG_ERROR | (pel->myImage->flags & ~(IMG_ERROR|IMG_MISSING|IMG_NOTLOADED));
|
|
}
|
|
}
|
|
if (pParams->pDest) Dest_DestroyDest(pParams->pDest);
|
|
}
|
|
if (pParams->pRequest) HTRequest_delete(pParams->pRequest);
|
|
}
|
|
break;
|
|
}
|
|
/*
|
|
sometimes you feel like a nut, sometimes you don't
|
|
but really, quick exit so flag won't move as much on a revisit to a page
|
|
where everything is cached
|
|
*/
|
|
LoadBackgroundBlobs_Async_Exit:
|
|
/*reduce outstanding thread count so flag functions properly*/
|
|
if (pParams->pImgThreads){
|
|
pParams->pImgThreads = pSafeFree(pParams->pImgThreads);
|
|
if (pParams->pImgThreads) Async_UnblockThread(pParams->thidParent);
|
|
}
|
|
if (pParams->pDecoder) makeAvailable((PDECODER)pParams->pDecoder);
|
|
}
|
|
return STATE_DONE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: GTR_DownloadBackgroundBlob
|
|
|
|
SYNOPSIS: Called when we are about to start downloading a
|
|
background sound file
|
|
|
|
********************************************************************/
|
|
HTStream *GTR_DownloadBackgroundBlob(struct Mwin *tw, HTRequest *request, void *param, HTFormat input_format, HTFormat output_format, HTStream *output_stream){
|
|
PBGBLOBPARAMS pBGBlobParams;
|
|
BOOL bWasSilent;
|
|
HTStream *me;
|
|
|
|
request = HTRequest_validate(request);
|
|
if (!request) return NULL;
|
|
|
|
pBGBlobParams = (PBGBLOBPARAMS) request->context;
|
|
|
|
if (request->szLocalFileName){
|
|
request->savefile = GTR_strdup(request->szLocalFileName);
|
|
}
|
|
else{
|
|
if (!request->fImgFromDCache)
|
|
{
|
|
// generate a temporary file name. This will be in the DCache
|
|
// directory but have some random .TMP name. This will get renamed
|
|
// by the caching code later on so it arrives at its proper name
|
|
// in the cache. A little weird, huh?
|
|
if (!(request->savefile = (char *) GTR_MALLOC(_MAX_PATH + 1))) {
|
|
BGBLOBPARAMSDestruct(pBGBlobParams);
|
|
return NULL;
|
|
}
|
|
|
|
// Get a temporary file name
|
|
if ( GetTempFileName(gPrefs.szCacheLocation, "A", 0, request->savefile) == 0 ) {
|
|
char path[_MAX_PATH + 1] = "";
|
|
PREF_GetTempPath(_MAX_PATH, path);
|
|
GetTempFileName(path, "A", 0, request->savefile);
|
|
}
|
|
}
|
|
}
|
|
|
|
pBGBlobParams->szRequestedURL = GTR_strdup(request->destination->szRequestedURL);
|
|
if (pBGBlobParams->szRequestedURL){
|
|
pBGBlobParams->OriginalFormat = input_format;
|
|
pBGBlobParams->pszFilePath = request->savefile;
|
|
request->nosavedlg = TRUE;
|
|
|
|
/*
|
|
get state of stuff downloading in background
|
|
we do this because HTSaveWithCallback puts up that annoying message
|
|
when we are downloading and want to switch pages.
|
|
we supress this behavior if a BLOB was the cause
|
|
*/
|
|
bWasSilent = tw->bSilent;
|
|
|
|
if (WWW_BGSOUND == request->output_format) pBGBlobParams->pCallback = BackgroundSoundFile_Callback;
|
|
else if (WWW_MCI == request->output_format) pBGBlobParams->pCallback = BackgroundMciFile_Callback;
|
|
#ifdef FEATURE_VRML
|
|
else if (WWW_VRML == request->output_format) pBGBlobParams->pCallback = BackgroundVRMLFile_Callback;
|
|
#endif
|
|
else if (WWW_PRESENT == request->output_format) pBGBlobParams->pCallback = BackgroundVRMLFile_Callback;
|
|
else ASSERT(0);
|
|
|
|
me = HTSaveWithCallback(tw, request,pBGBlobParams, input_format, BackgroundBlob_Callback);
|
|
tw->bSilent = bWasSilent;
|
|
}
|
|
else me = NULL;
|
|
|
|
return me;
|
|
}
|
|
|
|
|
|
/*Helper Functions for external structures----------------------------------*/
|
|
PBLOBstuff BlobConstruct(){
|
|
PBLOBstuff pblob;
|
|
|
|
pblob = GTR_MALLOC(sizeof(*pblob));
|
|
memset(pblob, 0, sizeof(*pblob));
|
|
return pblob;
|
|
}
|
|
|
|
void BlobDestruct(PBLOBstuff pblob){
|
|
if (pblob){
|
|
if (pblob->szURL) GTR_FREE(pblob->szURL);
|
|
if (pblob->szFileName) GTR_FREE(pblob->szFileName);
|
|
GTR_FREE(pblob);
|
|
}
|
|
}
|
|
|
|
BOOL BlobStoreUrl(PBLOBstuff pblob, char *pURL){
|
|
ASSERT(pblob);
|
|
if (pblob->szURL) GTR_FREE(pblob->szURL);
|
|
pblob->szURL = GTR_strdup(pURL);
|
|
return pblob->szURL?TRUE:FALSE;
|
|
}
|
|
|
|
BOOL BlobStoreFileName(PBLOBstuff pblob, char *pFileName){
|
|
ASSERT(pblob);
|
|
if (pblob->szFileName) GTR_FREE(pblob->szFileName);
|
|
pblob->szFileName = GTR_strdup(pFileName);
|
|
return pblob->szFileName?TRUE:FALSE;
|
|
}
|
|
|
|
|
|
/* Remove all blobs in a document
|
|
* Sort of replicated from Image_NukeImages && blob part of Image_Nuke
|
|
*/
|
|
BOOL FNukeBlobs(struct _www *pW3doc, BOOL bNukeDCache)
|
|
{
|
|
int i;
|
|
BOOL fSomethingNuked = FALSE;
|
|
|
|
for (i = 0; i < pW3doc->elementCount; i++)
|
|
{
|
|
if (pW3doc->aElements[i].pblob)
|
|
{
|
|
if (bNukeDCache && pW3doc->aElements[i].pblob->szURL )
|
|
{
|
|
pW3doc->aElements[i].pblob->dwFlags &= ~( BLOB_FLAGS_LOADING | BLOB_FLAGS_FIXUP | BLOB_FLAGS_LOADED );
|
|
FlushDCacheEntry(pW3doc->aElements[i].pblob->szURL);
|
|
fSomethingNuked = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return fSomethingNuked;
|
|
}
|
|
|