2010-04-25 00:31:27 +00:00
|
|
|
/*
|
2009-02-09 21:15:56 +00:00
|
|
|
* Copyright (C) 2007-2009 Gabest
|
|
|
|
* http://www.gabest.org
|
|
|
|
*
|
|
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
* any later version.
|
2010-04-25 00:31:27 +00:00
|
|
|
*
|
2009-02-09 21:15:56 +00:00
|
|
|
* This Program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
2010-04-25 00:31:27 +00:00
|
|
|
*
|
2009-02-09 21:15:56 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with GNU Make; see the file COPYING. If not, write to
|
2010-04-25 00:31:27 +00:00
|
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
2009-02-09 21:15:56 +00:00
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2011-02-19 03:36:30 +00:00
|
|
|
#include "stdafx.h"
|
2009-02-09 21:15:56 +00:00
|
|
|
#include "GSTextureCacheSW.h"
|
|
|
|
|
|
|
|
GSTextureCacheSW::GSTextureCacheSW(GSState* state)
|
|
|
|
: m_state(state)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
GSTextureCacheSW::~GSTextureCacheSW()
|
|
|
|
{
|
|
|
|
RemoveAll();
|
|
|
|
}
|
|
|
|
|
2011-12-18 08:13:20 +00:00
|
|
|
GSTextureCacheSW::Texture* GSTextureCacheSW::Lookup(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, uint32 tw0)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
|
|
|
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
Texture* t = NULL;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
list<Texture*>& m = m_map[TEX0.TBP0 >> 5];
|
2009-06-27 03:32:33 +00:00
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
for(list<Texture*>::iterator i = m.begin(); i != m.end(); i++)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2011-03-17 02:55:20 +00:00
|
|
|
Texture* t2 = *i;
|
2009-05-11 08:18:00 +00:00
|
|
|
|
2009-07-04 15:14:04 +00:00
|
|
|
if(((TEX0.u32[0] ^ t2->m_TEX0.u32[0]) | ((TEX0.u32[1] ^ t2->m_TEX0.u32[1]) & 3)) != 0) // TBP0 TBW PSM TW TH
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-06-17 11:24:42 +00:00
|
|
|
if((psm.trbpp == 16 || psm.trbpp == 24) && TEX0.TCC && TEXA != t2->m_TEXA)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
if(tw0 != 0 && t2->m_tw != tw0)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-07-12 13:46:05 +00:00
|
|
|
m.splice(m.begin(), m, i);
|
|
|
|
|
2009-02-09 21:15:56 +00:00
|
|
|
t = t2;
|
|
|
|
|
|
|
|
t->m_age = 0;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(t == NULL)
|
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
t = new Texture(m_state, tw0, TEX0, TEXA);
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-03-12 22:10:58 +00:00
|
|
|
m_textures.insert(t);
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
for(list<uint32>::iterator i = t->m_pages.n.begin(); i != t->m_pages.n.end(); i++)
|
2009-07-01 22:29:24 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
m_map[*i].push_front(t);
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
void GSTextureCacheSW::InvalidateVideoMem(GSOffset* o, const GSVector4i& rect)
|
2009-07-22 03:55:28 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
list<uint32>* pages = o->GetPages(rect);
|
2009-07-22 03:55:28 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
for(list<uint32>::iterator p = pages->begin(); p != pages->end(); p++)
|
2009-07-22 03:55:28 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
uint32 page = *p;
|
|
|
|
|
|
|
|
const list<Texture*>& map = m_map[page];
|
2009-07-22 03:55:28 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
for(list<Texture*>::const_iterator i = map.begin(); i != map.end(); i++)
|
2009-07-22 03:55:28 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
Texture* t = *i;
|
2009-07-22 03:55:28 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
if(GSUtil::HasSharedBits(o->psm, t->m_TEX0.PSM))
|
2009-07-22 03:55:28 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
if(t->m_repeating)
|
2009-07-22 03:55:28 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
list<GSVector2i>& l = t->m_p2t[page];
|
2011-04-25 18:18:21 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
for(list<GSVector2i>::iterator j = l.begin(); j != l.end(); j++)
|
|
|
|
{
|
|
|
|
t->m_valid[j->x] &= j->y;
|
2009-07-22 03:55:28 +00:00
|
|
|
}
|
|
|
|
}
|
2011-12-23 15:53:53 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
t->m_valid[page] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
t->m_complete = false;
|
2009-07-22 03:55:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-02-09 21:15:56 +00:00
|
|
|
void GSTextureCacheSW::RemoveAll()
|
|
|
|
{
|
2009-07-16 21:36:07 +00:00
|
|
|
for_each(m_textures.begin(), m_textures.end(), delete_object());
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2009-05-11 08:18:00 +00:00
|
|
|
m_textures.clear();
|
2009-02-09 21:15:56 +00:00
|
|
|
|
|
|
|
for(int i = 0; i < MAX_PAGES; i++)
|
|
|
|
{
|
2009-05-11 08:18:00 +00:00
|
|
|
m_map[i].clear();
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
void GSTextureCacheSW::RemoveAt(Texture* t)
|
2009-07-12 13:46:05 +00:00
|
|
|
{
|
|
|
|
m_textures.erase(t);
|
|
|
|
|
|
|
|
for(uint32 start = t->m_TEX0.TBP0 >> 5, end = countof(m_map) - 1; start <= end; start++)
|
|
|
|
{
|
2011-03-17 02:55:20 +00:00
|
|
|
list<Texture*>& m = m_map[start];
|
2009-07-12 13:46:05 +00:00
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
for(list<Texture*>::iterator i = m.begin(); i != m.end(); )
|
2009-07-12 13:46:05 +00:00
|
|
|
{
|
2011-03-17 02:55:20 +00:00
|
|
|
list<Texture*>::iterator j = i++;
|
2009-07-12 13:46:05 +00:00
|
|
|
|
|
|
|
if(*j == t) {m.erase(j); break;}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete t;
|
|
|
|
}
|
|
|
|
|
2009-02-09 21:15:56 +00:00
|
|
|
void GSTextureCacheSW::IncAge()
|
|
|
|
{
|
2011-03-17 02:55:20 +00:00
|
|
|
for(hash_set<Texture*>::iterator i = m_textures.begin(); i != m_textures.end(); )
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2011-03-17 02:55:20 +00:00
|
|
|
hash_set<Texture*>::iterator j = i++;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
Texture* t = *j;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2009-05-07 06:32:10 +00:00
|
|
|
if(++t->m_age > 30)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2009-07-12 13:46:05 +00:00
|
|
|
RemoveAt(t);
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
GSTextureCacheSW::Texture::Texture(GSState* state, uint32 tw0, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA)
|
2009-02-09 21:15:56 +00:00
|
|
|
: m_state(state)
|
|
|
|
, m_buff(NULL)
|
2011-03-17 02:55:20 +00:00
|
|
|
, m_tw(tw0)
|
2009-02-09 21:15:56 +00:00
|
|
|
, m_age(0)
|
2009-05-07 06:32:10 +00:00
|
|
|
, m_complete(false)
|
2011-04-26 00:56:54 +00:00
|
|
|
, m_p2t(NULL)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2011-04-25 18:18:21 +00:00
|
|
|
m_TEX0 = TEX0;
|
|
|
|
m_TEXA = TEXA;
|
|
|
|
|
2009-02-09 21:15:56 +00:00
|
|
|
memset(m_valid, 0, sizeof(m_valid));
|
2011-12-23 15:53:53 +00:00
|
|
|
memset(m_pages.bm, 0, sizeof(m_pages.bm));
|
2011-12-18 08:13:20 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
m_offset = m_state->m_mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM);
|
2011-12-18 08:13:20 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
list<uint32>* pages = m_offset->GetPages(GSVector4i(0, 0, 1 << TEX0.TW, 1 << TEX0.TH));
|
2011-12-18 08:13:20 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
for(list<uint32>::iterator i = pages->begin(); i != pages->end(); i++)
|
2011-12-18 08:13:20 +00:00
|
|
|
{
|
2011-12-23 15:53:53 +00:00
|
|
|
uint32 page = *i;
|
2011-12-18 08:13:20 +00:00
|
|
|
|
2011-12-23 15:53:53 +00:00
|
|
|
m_pages.bm[page >> 5] |= 1 << (page & 31);
|
|
|
|
m_pages.n.push_back(page);
|
2011-12-18 08:13:20 +00:00
|
|
|
}
|
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
m_repeating = m_TEX0.IsRepeating(); // repeating mode always works, it is just slightly slower
|
2011-04-26 00:56:54 +00:00
|
|
|
|
|
|
|
if(m_repeating)
|
|
|
|
{
|
|
|
|
m_p2t = m_state->m_mem.GetPage2TileMap(m_TEX0);
|
|
|
|
}
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
GSTextureCacheSW::Texture::~Texture()
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
|
|
|
if(m_buff)
|
|
|
|
{
|
|
|
|
_aligned_free(m_buff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
bool GSTextureCacheSW::Texture::Update(const GSVector4i& rect)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2009-05-07 06:32:10 +00:00
|
|
|
if(m_complete)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_TEX0.PSM];
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2009-07-04 15:14:04 +00:00
|
|
|
GSVector2i bs = psm.bs;
|
|
|
|
|
2011-03-12 22:10:58 +00:00
|
|
|
int shift = psm.pal == 0 ? 2 : 0;
|
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
int tw = std::max<int>(1 << m_TEX0.TW, bs.x);
|
|
|
|
int th = std::max<int>(1 << m_TEX0.TH, bs.y);
|
2009-07-04 15:14:04 +00:00
|
|
|
|
2011-03-12 22:10:58 +00:00
|
|
|
GSVector4i r = rect;
|
|
|
|
|
|
|
|
r = r.ralign<Align_Outside>(bs);
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2009-07-04 15:14:04 +00:00
|
|
|
if(r.eq(GSVector4i(0, 0, tw, th)))
|
|
|
|
{
|
|
|
|
m_complete = true; // lame, but better than nothing
|
|
|
|
}
|
2009-02-09 21:15:56 +00:00
|
|
|
|
|
|
|
if(m_buff == NULL)
|
|
|
|
{
|
2011-04-26 00:56:54 +00:00
|
|
|
uint32 tw0 = std::max<int>(m_TEX0.TW, 5 - shift); // makes one row 32 bytes at least, matches the smallest block size that is allocated for m_buff
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
if(m_tw == 0)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2011-03-17 02:55:20 +00:00
|
|
|
m_tw = tw0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ASSERT(m_tw >= tw0);
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
uint32 pitch = (1 << m_tw) << shift;
|
|
|
|
|
|
|
|
m_buff = _aligned_malloc(pitch * th * 4, 32);
|
2011-03-12 22:10:58 +00:00
|
|
|
|
2011-03-17 02:55:20 +00:00
|
|
|
if(m_buff == NULL)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
|
2009-07-04 15:14:04 +00:00
|
|
|
GSLocalMemory& mem = m_state->m_mem;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-03-12 22:10:58 +00:00
|
|
|
const GSOffset* RESTRICT o = m_offset;
|
2009-07-02 16:05:03 +00:00
|
|
|
|
|
|
|
uint32 blocks = 0;
|
|
|
|
|
2011-03-12 22:10:58 +00:00
|
|
|
GSLocalMemory::readTextureBlock rtxbP = psm.rtxbP;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2009-07-02 16:05:03 +00:00
|
|
|
uint32 pitch = (1 << m_tw) << shift;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
uint8* dst = (uint8*)m_buff + pitch * r.top;
|
2009-07-02 16:05:03 +00:00
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
if(m_repeating)
|
|
|
|
{
|
2011-04-25 01:44:00 +00:00
|
|
|
for(int y = r.top, block_pitch = pitch * bs.y; y < r.bottom; y += bs.y, dst += block_pitch)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2011-04-25 01:44:00 +00:00
|
|
|
uint32 base = o->block.row[y >> 3];
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
for(int x = r.left, i = (y << 7) + x; x < r.right; x += bs.x, i += bs.x)
|
2009-02-09 21:15:56 +00:00
|
|
|
{
|
2011-04-25 01:44:00 +00:00
|
|
|
uint32 block = base + o->block.col[x >> 3];
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-04-25 01:44:00 +00:00
|
|
|
if(block < MAX_BLOCKS)
|
2009-05-09 08:37:02 +00:00
|
|
|
{
|
2011-04-25 18:18:21 +00:00
|
|
|
uint32 addr = i >> 3;
|
|
|
|
|
|
|
|
uint32 row = addr >> 5;
|
|
|
|
uint32 col = 1 << (addr & 31);
|
2011-04-25 01:44:00 +00:00
|
|
|
|
|
|
|
if((m_valid[row] & col) == 0)
|
2009-05-09 08:37:02 +00:00
|
|
|
{
|
|
|
|
m_valid[row] |= col;
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
(mem.*rtxbP)(block, &dst[x << shift], pitch, m_TEXA);
|
2009-02-09 21:15:56 +00:00
|
|
|
|
2011-04-25 01:44:00 +00:00
|
|
|
blocks++;
|
|
|
|
}
|
2009-05-09 08:37:02 +00:00
|
|
|
}
|
|
|
|
}
|
2009-05-07 06:32:10 +00:00
|
|
|
}
|
|
|
|
}
|
2011-04-25 01:44:00 +00:00
|
|
|
else
|
2009-07-02 16:05:03 +00:00
|
|
|
{
|
2011-04-25 18:18:21 +00:00
|
|
|
for(int y = r.top, block_pitch = pitch * bs.y; y < r.bottom; y += bs.y, dst += block_pitch)
|
2009-05-07 06:32:10 +00:00
|
|
|
{
|
2011-04-25 01:44:00 +00:00
|
|
|
uint32 base = o->block.row[y >> 3];
|
|
|
|
|
|
|
|
for(int x = r.left; x < r.right; x += bs.x)
|
2009-05-07 06:32:10 +00:00
|
|
|
{
|
2011-04-25 01:44:00 +00:00
|
|
|
uint32 block = base + o->block.col[x >> 3];
|
2009-05-09 08:37:02 +00:00
|
|
|
|
2011-04-25 01:44:00 +00:00
|
|
|
if(block < MAX_BLOCKS)
|
2009-05-09 08:37:02 +00:00
|
|
|
{
|
2011-04-25 01:44:00 +00:00
|
|
|
uint32 row = block >> 5;
|
|
|
|
uint32 col = 1 << (block & 31);
|
2009-05-09 08:37:02 +00:00
|
|
|
|
2011-04-25 01:44:00 +00:00
|
|
|
if((m_valid[row] & col) == 0)
|
2009-05-09 08:37:02 +00:00
|
|
|
{
|
2009-07-02 16:05:03 +00:00
|
|
|
m_valid[row] |= col;
|
2011-04-25 01:44:00 +00:00
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
(mem.*rtxbP)(block, &dst[x << shift], pitch, m_TEXA);
|
2011-04-25 01:44:00 +00:00
|
|
|
|
2011-04-25 18:18:21 +00:00
|
|
|
blocks++;
|
2009-05-09 08:37:02 +00:00
|
|
|
}
|
|
|
|
}
|
2009-05-07 06:32:10 +00:00
|
|
|
}
|
2009-05-09 08:37:02 +00:00
|
|
|
}
|
2011-04-25 01:44:00 +00:00
|
|
|
}
|
2009-05-07 06:32:10 +00:00
|
|
|
|
2011-04-25 01:44:00 +00:00
|
|
|
if(blocks > 0)
|
|
|
|
{
|
2009-07-04 15:14:04 +00:00
|
|
|
m_state->m_perfmon.Put(GSPerfMon::Unswizzle, bs.x * bs.y * blocks << shift);
|
2009-02-09 21:15:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2009-05-07 06:32:10 +00:00
|
|
|
}
|
2011-03-17 02:55:20 +00:00
|
|
|
|
|
|
|
#include "GSTextureSW.h"
|
|
|
|
|
|
|
|
bool GSTextureCacheSW::Texture::Save(const string& fn, bool dds) const
|
|
|
|
{
|
|
|
|
const uint32* RESTRICT clut = m_state->m_mem.m_clut;
|
|
|
|
|
|
|
|
int w = 1 << m_TEX0.TW;
|
|
|
|
int h = 1 << m_TEX0.TH;
|
|
|
|
|
|
|
|
GSTextureSW t(0, w, h);
|
|
|
|
|
|
|
|
GSTexture::GSMap m;
|
|
|
|
|
|
|
|
if(t.Map(m, NULL))
|
|
|
|
{
|
|
|
|
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_TEX0.PSM];
|
|
|
|
|
|
|
|
const uint8* RESTRICT src = (uint8*)m_buff;
|
|
|
|
int pitch = 1 << (m_tw + (psm.pal == 0 ? 2 : 0));
|
|
|
|
|
|
|
|
for(int j = 0; j < h; j++, src += pitch, m.bits += m.pitch)
|
|
|
|
{
|
|
|
|
if(psm.pal == 0)
|
|
|
|
{
|
|
|
|
memcpy(m.bits, src, sizeof(uint32) * w);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for(int i = 0; i < w; i++)
|
|
|
|
{
|
|
|
|
((uint32*)m.bits)[i] = clut[((uint8*)src)[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Unmap();
|
|
|
|
|
|
|
|
return t.Save(fn.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|