提供某api两个(OAuth认证)

低调,要低调。

阅读全文…

4,309 次浏览 | 没有评论
2010年10月16日 | 归档于 android
标签: , ,

二次会(にじかい)

这个周末第一次感受日本的酒文化。由于周六是假日,因此周五晚上去一起喝高大概是会社的一个传统。虽然今天是入社的日子,也不例外。这样的日子每月大概有四次,ちょっと大変。

第一次去的店在銀座附近,据说是还算便宜的,3500円飲み放題。于是很快,一半人就喝高了。清酒刺激虽然少,但非常容易上头。10人一直喝到11点,非常high,到最后都理解不能了,只能拼命点头。估计蹩脚的日语也是满嘴胡说。这个店里还有中国的女孩子在打工。

前往二次会早已经不知东南西北,只感觉走了很长路。半路去7-11喝了点醒酒药,还不小心把伞掉在路上,被后面的同事踩了一下然后就坏掉了,300円店果然没好货。二次会的居酒屋,脱鞋进入和室,还怕找不着鞋,好在最后没事。二次会收敛了很多,吃喝的东西远远比不上第一次,但价格却不便宜,普通的果汁一杯就500円。由于没喝酒,出来时头醒了很多。大概每人消费了3000多。两次共计将近1W。

被拉到新橋駅站坐JR的地铁,到東京駅转東京メトロ的丸の内線,居然回到了家,感觉是个奇迹。丸の内線每过一站,人就多一点,虽然是最后一班车,但到最后简直就像高峰期的电车,头埋着的都是喝高了的,电车里吱吱喳喳,和平时ぜんぜん違う。

感觉如果三次会四次会之类,真的会明天躺在大街上

3,899 次浏览 | 没有评论
2010年10月2日 | 归档于 私语
标签:

東京に自分で暮らすことが始まります

28号下午抵达东京。自己在东京的生活正式开始。

这两天的见闻和印象:
机场:成田机场离市区非常远,坐机场大巴返回后乐园的DOME HOTEL花了两个小时,3000日元,稍贵了点。大巴非常非常安静
地铁:线内160,转换一次线路只用加30,余的出行还没有超过190日元的。地铁卡非常便利,但持卡不会打折,卡工本费是500日元。电车上也很安静,偶见年轻人打电话。
自动扶梯:靠左站立,留出右边通道给赶时间的人
饮食:饮料100~200日元之间,基本上是自动贩卖机上购买。吃饭:大多数店需要在店外的自动贩卖机购买食券,再在店内交给店员,KFC则和国内一样。吃饭的价格基本在300~1000日元之间。不知道是否面条都叫拉面,昨晚吃的拉面明显不是手工拉制的。盖饭之类的定食大概在500日元上下
购物:住所靠近池袋,两天的购物都是前往池袋。布团套装花了1W,唔~好贵。池袋的Sunshine City有个百元店,在那扫了一堆日常生活日品,直到拿不动。在3coins买了把长伞。日本人好像都喜欢拿长伞,说是折伞使用后容易弄湿手,一般在玄关处也都有放置长伞的架子。在ビックカメラ买了个电源转换接头,国内的三线电源插头拿到日本都不能直接使用,圆头的插头也不行。
住:礼金平摊后每个月需要支付8w多日元,网络无料(光速中),水电费另算。屋租是最大的一笔支出。小公寓内装修还不错,设备很齐全,电视机古老了一点,居然集成录像带播放功能,而且只有12个频道。洗衣服要投币,有点仆街。由于靠近大马路,稍有点吵。
街道:非常干净,虽然说不上完全没有垃圾,道路和行人道都是柏油铺砌。行人和汽车都很遵守交通规则,车会让人,以致过小路口的斑马线时还停留下来让车先过。很容易迷路,特别是晚上。日本的学校放学相当早,三点多就能见到穿着校服的中学生,而且到了晚上还能大量见到(放学后不许穿着校服游荡是哪里的规定啊?)。不能不说,泥轰精穿着真的有泥轰的气质,今天数次被人一眼认出是中国人
受姬:杯具中,没有银行卡不能办理。软银的店倒是到处都有,iphone4很帅,把玩了一下,iphone4需要预约,2周后能取。不过想买HTC Desire,同样需要预约。不知受姬何时能入手
银行卡:杯具中,卡好像要两周后才能发下来
外国人登陆证:住的文京区的役所在后乐园附近,手续费300,同样需要等两周才能取。后乐园的遊園地貌似很好玩,过山车溜过,下面的店仿佛地震中

两天花掉了4w日元。

灭有受姬,相机又留给家里了,不能上图稍微残念了点

3,991 次浏览 | 没有评论
2010年9月29日 | 归档于 私语
标签:

ANSI2Unicode 1.0.3正式版发布

ANSI2Unicode是开源项目UniCue的组成部分。ANSI2Unicode致力于文档编码的转换,通过自定义编码映射表,目前支持Shift-JIS、GBK、BIG5三种编码到utf-8的转换。

ANSI2Unicode 1.0.3 功能特性:
–完整的Unicode支持,不必为文档路径中的特殊字符担心
–自定义BIG5 to Unicode的映射表,取代微软自身的CP950,解决日文假名丢失问题
–转换结果保存为utf-8编码的文本
–自动检测文本编码
–支持文件拖放操作及命令行打开文档
–自动修正cue中的File行音频文件扩展名
–自动修正cue中的File行旧式“The True Audio”标签为“WAVE”
–不改变默认打开程序的txt/log/cue右键菜单关联
–提取tak/flac/ape的内嵌cue,并自动修正cue中的音频文件名
–转换字符串功能
–转换字符串模式下,支持拖放乱码文件名进行转换

下载:http://code.google.com/p/unicue/

源码:http://unicue.googlecode.com/svn/trunk/

bug反馈或特性提出可直接在blog回复或前往http://code.google.com/p/unicue/issues/list

转载扩散大欢迎

19,553 次浏览 | 5 条评论
2010年9月11日 | 归档于 程序

flac的内嵌cue提取

flac的内嵌cue实际上有两个存放位置。按照flac官方的技术文档,flac的文件结构中METADATA_BLOCK可以存放CUESHEET类型的数据,不过这种形式的内嵌cue相当复杂,余抛弃实现提取这种cue了。另外METADATA_BLOCK还可以存放VORBIS_COMMENT类型的数据,数据部中“cuesheet=”后面的即是cue,foobar2000调用flac.exe进行编码并写入内嵌cue时,是写到VORBIS_COMMENT中。存放到VORBIS_COMMENT中似乎更普遍一点。

如果不按照flac的文件结构,用稍微暴力一点的方法提取,也是可以的,这时就和提取tak的内嵌cue非常类似了,只不过flac的位于文件头部。

简单说一下flac的METADATA_BLOCK的结构。一个METADATA_BLOCK由两部分组成,METADATA_BLOCK_HEADER和METADATA_BLOCK_DATA。METADATA_BLOCK_HEADER为固定的四字节(32bit),第一个bit为判断是否是最后一个METADATA_BLOCK,第二至第八bit为METADATA_BLOCK的类型,VORBIS_COMMENT的类型为4,后面的3个字节是METADATA_BLOCK_DATA的长度。

因为只注重内嵌cue,所以只有遇到VORBIS_COMMENT类型,才开始进行cuesheet的捕捉,否则一直跳过。跳过时文件指针移动的偏移量即是METADATA_BLOCK_DATA的长度。

代码:

    CFile OpenFile;
    if (!OpenFile.Open(FlacPathName,CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary))
    {
        OpenFile.Close();
        ::AfxMessageBox(_T("打开失败!"),MB_OK);
        return FALSE;
    }

    if (OpenFile.GetLength()<1048576) // 小于1M,文档太小了
    {
        OpenFile.Close();
        return FALSE;
    }

    unsigned char Header[5];
    memset(Header,0,5);
    ULONGLONG position=0;
    //4个字节的头部
    OpenFile.Read((void*)Header,4);
    if (strcmp((char*)Header,"fLaC")!=0)
    {
        AfxMessageBox(_T("Not real flac file!"));
        return FALSE;
    }

    unsigned char chr;
    unsigned char *Buffer=NULL;
    UINT Length;
    //4个字节的METADATA_BLOCK_HEADER
    do
    {
        OpenFile.Read((void*)Header,4);
        //解析
        memcpy(&chr,Header,1);
        //检查最高位是否为1
        if ((chr&0x80)==0x80)
        {
            //最后一个METADATA_BLOCK
            if ((chr&0x7F)==0x04)//是VORBIS_COMMENT
            {
                //读取BLOCK长度
                Length=Header[1]*0x10000+Header[2]*0x100+Header[3];
                //申请空间
                Buffer=new unsigned char[Length+1];
                //读取BLOCK DATA
                OpenFile.Read((void*)Buffer,Length);
                Buffer[Length]='\0';
            }
            break;
        }
        else
        {
            //不是最后一个METADATA_BLOCK
            if ((chr&0x7F)==0x04)//是VORBIS_COMMENT
            {
                //读取BLOCK长度
                Length=Header[1]*0x10000+Header[2]*0x100+Header[3];
                //申请空间
                Buffer=new unsigned char[Length+1];
                //读取BLOCK DATA
                OpenFile.Read((void*)Buffer,Length);
                Buffer[Length]='\0';
                break;
            }
            else //不是VORBIS_COMMENT
            {
                //读取BLOCK长度
                Length=Header[1]*0x10000+Header[2]*0x100+Header[3];
                //移动文件指针
                OpenFile.Seek(Length,CFile::current);
                position=OpenFile.GetPosition();
            }
        }
    } while(position<=1048576);

    OpenFile.Close();
    if (!Buffer)
        return FALSE;

    //查找 Cuesheet 标记,自动机模型,大小写不敏感
    int state=0,BeginPos=0,EndPos=0;
    for (UINT i=0;i<Length;++i)
    {
        if ((Buffer[i]>=0x41)&&(Buffer[i]<=0x5A))
            Buffer[i]=Buffer[i]+0x20;

        switch (Buffer[i])
        {
        case 'c':
            state=1;      //C
            break;
        case 'u':
            if (state==1)
                state=2;  //Cu
            else
                state=0;
            break;
        case 'e':
            switch (state)
            {
            case 2:
                state=3;  //Cue
                break;
            case 5:
                state=6;  //Cueshe
                break;
            case 6:
                state=7;  //Cueshee
                break;
            default:
                state=0;
            }
            break;
        case 's':
            if (state==3)
                state=4;  //Cues
            else
                state=0;
            break;
        case 'h':
            if (state==4)
                state=5;  //Cuesh
            else
                state=0;
            break;
        case 't':
            if (state==7)
            {
                state=8;  //Cuesheet
            }
            else
                state=0;
            break;
        default:
            state=0;
        }
        if (state==8)
        {
            BeginPos=i+2;
            break;
        }
    }
    if (BeginPos==0)
    {
        delete []Buffer;
        return FALSE;
    }
    //查找终止符 0D 0A ? 00 00 00(连续3个终止符以上)
    state=0;
    for (int i=BeginPos;i<20480;++i)
    {
        switch (Buffer[i])
        {
        case '\0':
            state++;
            break;
        default:
            state=0;
        }
        if (state==3)
        {
            EndPos=i-3; //指向0D 0A后的第一个字符
            break;
        }
    }

    if (EndPos<=1)
    {
        delete []Buffer;
        return FALSE;
    }

    if ((Buffer[EndPos-2]=='\x0D')&&(Buffer[EndPos-1]=='\x0A'))
        EndPos--;

    int CueLength=EndPos-BeginPos+1;
    if (CueLength<=10) //too short
    {
        delete []Buffer;
        return FALSE;
    }

    unsigned char *CueString;
    CueString=new unsigned char[CueLength+1];
    memcpy(CueString,Buffer+BeginPos,CueLength);
    CueString[CueLength]='\0';

    delete []CueString;
    delete []Buffer;
    return TRUE;

CueString中存放的即是utf-8编码的cue字符串,写入到文件即可。
捕捉cuesheet标记的方法和tak一样的。

8,218 次浏览 | 没有评论
2010年9月9日 | 归档于 技术, 程序
标签: , , , , ,

tak的内嵌cue提取

tak的内嵌cue提取非常简单。内嵌的cue位于文件尾部,以utf-8编码存放。只要捕捉到起始点和结束点,拷贝出来即可。
内嵌cue的起始标记是Cuesheet,大小写不敏感,结束标记是之后遇到的第一个连续终止符串(六个终止符以上)
为了不用读取整个tak文件,将文件指针移到最后20K处,将之后的数据拷贝出来,在这20K的数据中搜索。一般的cue也不会有那么长吧

	CFile OpenFile;
	if (!OpenFile.Open(TakFilePathName,CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary))
	{
		OpenFile.Close();
		::AfxMessageBox(_T("打开失败!"),MB_OK);
		return FALSE;
	}

	if (OpenFile.GetLength()<20480) // 小于20K,文档太小了
	{
		OpenFile.Close();
		return FALSE;
	}

	OpenFile.Seek(-20480,CFile::end);
	unsigned char Buffer[20480]; //20k的缓冲区
	memset(Buffer,0,20480);
	OpenFile.Read(Buffer,20480);
	OpenFile.Close();

	//查找 Cuesheet 标记,自动机模型,大小写不敏感
	int state=0,BeginPos=0,EndPos=0,Length=0;
	for (int i=0;i<20480;++i)
	{
		if ((Buffer[i]>=0x41)&&(Buffer[i]<=0x5A))
			Buffer[i]=Buffer[i]+0x20;

		switch (Buffer[i])
		{
		case 'c':
			state=1;      //C
			break;
		case 'u':
			if (state==1)
				state=2;  //Cu
			else
				state=0;
			break;
		case 'e':
			switch (state)
			{
			case 2:
				state=3;  //Cue
				break;
			case 5:
				state=6;  //Cueshe
				break;
			case 6:
				state=7;  //Cueshee
				break;
			default:
				state=0;
			}
			break;
		case 's':
			if (state==3)
				state=4;  //Cues
			else
				state=0;
			break;
		case 'h':
			if (state==4)
				state=5;  //Cuesh
			else
				state=0;
			break;
		case 't':
			if (state==7)
			{
				state=8;  //Cuesheet
			}
			else
				state=0;
			break;
		default:
			state=0;
		}
		if (state==8)
		{
			BeginPos=i+2;
			break;
		}
	}

	if (BeginPos==0)
		return FALSE;

	//查找终止符 0D 0A ? 00 00 00 00 00 00 (连续六个终止符以上)
	state=0;
	for (int i=BeginPos;i<20480;++i)
	{
		switch (Buffer[i])
		{
		case '\0':
			state++;
			break;
		default:
			state=0;
		}
		if (state==6)
		{
			EndPos=i-6; //指向0D 0A后的第一个字符
			break;
		}
	}

	if (EndPos<=1)
		return FALSE;

	if ((Buffer[EndPos-2]=='\x0D')&&(Buffer[EndPos-1]=='\x0A'))
		EndPos--;

	Length=EndPos-BeginPos+1;
	if (Length<=10) //too short
		return FALSE;

	unsigned char *CueString;
	CueString=new unsigned char[Length+1];
	memcpy(CueString,Buffer+BeginPos,Length);
	CueString[Length]='\0';

	delete []CueString;
	return TRUE;

CueString中存放的即是utf-8编码的cue字符串,写入到文件即可。
这自动机模型捕捉Cuesheet写得很优美(擦汗

7,205 次浏览 | 没有评论
2010年9月6日 | 归档于 技术, 程序
标签: , , , , ,

tinyXml处理UTF-8编码详解——写入和读取

以前写过一篇博文介绍tinyXml输出utf-8文档

tinyXml的特点是不对xml节点内容的具体编码处理,这一切都交给用户。因此tinyXml和字符有关的函数都是只接受char*的数据类型。
例如:

TiXmlElement *pRoot=new TiXmlElement("test");
pRoot->SetAttribute("name","名字");

上述代码产生的节点,如果用TiXmlDocument的SaveFile函数直接保存,只能是ANSI的本地编码(无论程序是否是unicode),即使TiXmlDeclaration指定为utf-8。一种方法是输出到TiXmlPrinter,将TiXmlPrinter.CStr()转换到utf-8编码的char*后保存。

char*在双字节编码下是一种很奇特的字符串,中文平台下的VC的编译器,char*可以存放GBK汉字,编译能正确识别字符,因为ASCII码的最高位为0,而GBK双字节字符的首字节最高位为1。

在使用utf-8字符串时,必须树立一个观念:utf-8应当只在传输时使用,不适合作为函数过程的处理对象。什么是传输场合?网络传输和文件读写。以文件读写为例,文件以utf-8编码存放,在读入到内存后,应当立刻转换为unicode宽字符串。程序的内部处理过程中只有unicode宽字符串。直到写入文件时,unicode宽字符串才转换为utf-8字符串。

utf-8字符串本身是变长字符串,并没有特定的数据类型。它是以char*形式存放,它的byte的表现不符合任何双字节编码,当成双字节编码处理会立刻出错。事实上,char*只是一个存放空间,用void*、unsigned char*本质上没有区别。(倘若你喜欢,甚至可以拿char*来存放unicode宽字符串,一次memcpy两个byte就是了)。

脱离双字节编码(如GBK)的tinyXml使用方法是存在的。
例如上述代码可以改为:

TiXmlElement *pRoot=new TiXmlElement("test");
CStringA UTF8Str=CW2A(L"名字",CP_UTF8);
pRoot->SetAttribute("name",UTF8Str);

UTF8Str变量名即是内含的char*字符串的起始指针。CW2A函数可以自己写一个代替,并不难实现。此时可以直接调用TiXmlDocument的SaveFile函数保存为无BOM的UTF-8文档。要保存为含BOM的UTF-8文档,仍然需要TiXmlPrinter,但此时不需要对TiXmlPrinter.CStr()进行任何处理。

XmlEntityTree=new TiXmlDocument;
TiXmlDeclaration *dec=new TiXmlDeclaration("1.0","utf-8","");
XmlEntityTree->LinkEndChild(dec);
TiXmlElement *pRoot=new TiXmlElement("test");
CStringA UTF8Str=CW2A(L"名字",CP_UTF8);
pRoot->SetAttribute("name",UTF8Str);
XmlEntityTree->LinkEndChild(pRoot);
TiXmlPrinter printer;
XmlEntityTree->Accept(&printer);

char UTF8BOM[3]={'\xEF','\xBB','\xBF'};

CFile theFile;
theFile.Open(_T("test.xml"),CFile::modeCreate|CFile::modeWrite);
theFile.Write(UTF8BOM,3);
theFile.Write(printer.CStr(),strlen(printer.CStr()));
theFile.Close();

tinyXml在加载xml文档时有一个标记,TiXmlDocument.LoadFile(TiXmlEncoding encoding);
这个标记没多大作用,无论设为TIXML_ENCODING_UTF8还是TIXML_ENCODING_LEGACY,读入的节点的数据类型一样是char*。
设为TIXML_ENCODING_UTF8标记的唯一作用是tinyXml会自动处理文档的BOM。

对于下面文档,怎样才能正确读取到TemplateStr节点的内容?很简单,在读取时进行转换就行。

<?xml version="1.0" encoding="utf-8" ?>
<config>
    <TemplateStr>中文</TemplateStr>
    <AutoFixCue>true</AutoFixCue>
    <AutoFixTTA>true</AutoFixTTA>
    <AcceptDragFLAC>true</AcceptDragFLAC>
    <AcceptDragTAK>true</AcceptDragTAK>
    <AcceptDragAPE>true</AcceptDragAPE>
</config>
TiXmlDocument *xmlfile= new TiXmlDocument(FilePath);
xmlfile->LoadFile(TIXML_ENCODING_UTF8);

TiXmlHandle hRoot(xmlfile);
TiXmlElement *pElem;
TiXmlHandle hXmlHandle(0);

//config节点
pElem=hRoot.FirstChildElement().Element();
if (!pElem) return FALSE;
if (strcmp(pElem->Value(),"config")!=0)
	return FALSE;

//TemplateStr节点
hXmlHandle=TiXmlHandle(pElem);
pElem=hXmlHandle.FirstChild("TemplateStr").Element();
if (!pElem) return FALSE;
CString TemplateStr=UTF8toUnicode(pElem->GetText());

UTF8toUnicode函数:

CString UTF8toUnicode(const char* utf8Str,UINT length)
{
	CString unicodeStr;
	unicodeStr=_T("");

	if (!utf8Str)
		return unicodeStr;

	if (length==0)
		return unicodeStr;

	//转换
	WCHAR chr=0;
	for (UINT i=0;i<length;)
	{
		if ((0x80&utf8Str[i])==0) // ASCII
		{
			chr=utf8Str[i];
			i++;
		}
		else if((0xE0&utf8Str[i])==0xC0) // 110xxxxx 10xxxxxx
		{
			chr =(utf8Str[i+0]&0x3F)<<6;
			chr|=(utf8Str[i+1]&0x3F);
			i+=2;
		}
		else if((0xF0&utf8Str[i])==0xE0) // 1110xxxx 10xxxxxx 10xxxxxx
		{
			chr =(utf8Str[i+0]&0x1F)<<12;
			chr|=(utf8Str[i+1]&0x3F)<<6;
			chr|=(utf8Str[i+2]&0x3F);
			i+=3;
		}
		/*
		else if() // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
		{}
		else if() // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx  10xxxxxx
		{}
		else if() // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx  10xxxxxx 10xxxxxx 
		{}
		*/
		else // 不是UTF-8字符串
		{
			return unicodeStr;
		}
		unicodeStr.AppendChar(chr);
	}

	return unicodeStr;
}

CString UTF8toUnicode(const char* utf8Str)
{
	UINT theLength=strlen(utf8Str);
	return UTF8toUnicode(utf8Str,theLength);
}

strlen取char*的长度等于字节数(不含终止符),不是utf-8字串的真正字符个数。

18,009 次浏览 | 没有评论
2010年8月28日 | 归档于 技术, 程序

自制WordPress标签云页面

侧栏的标签云中的tag数量毕竟有限。于是很久前就想自制一个标签云页面。

复制所用主题下的page.php,命名tags.php;
在tags.php头部添加

<?php
/*
Template Name: tags
*/
?>

将tags.php中的<?php the_content(); ?>替换为<?php wp_tag_cloud(‘smallest=12&largest=40&unit=px&number=500’);?>
保存,上传至所用主题下的目录

后台添加新页面,模板选用tags。OVER

wp_tag_cloud是wp自身提供的函数,smallest是最小的字号(相关主题数最少),largest是最大的字号(相关主题数最多),unit是单位,number是显示的tag的最大数量

但对于blocks主题,还需要指定新的样式表。默认的content样式tag由于大小不一会重叠。
在style.css添加新样式

.tags-cloud{
	background:#FFF;
	padding:10px 11px 10px;
	overflow:hidden;
}

将wp_tag_cloud所在的div的class修改为tags-cloud。

余因为关闭了评论,所以同时移除了和评论相关的代码
最终的tags.php

<?php
/*
Template Name: tags
*/
?>

<?php get_header(); ?>

<?php if (have_posts()) : the_post(); ?>
	<div class="post">
		<h3 class="title"><?php the_title(); ?></h3>

		<div class="tags-cloud">
			<?php wp_tag_cloud('smallest=14&largest=46&unit=px&number=500');?>
			<div class="fixed"></div>
		</div>

		<div class="meta">
			<div class="act">
				<?php edit_post_link(__('Edit', 'blocks'), '', ''); ?>
			</div>
			<div class="info">
				<?php
					if (function_exists('the_views')) the_views(true, '', '');
				?>
			</div>
			<div class="fixed"></div>
		</div>
	</div>

<?php else: ?>
	<div class="block">
		<div class="content small r">
			<?php _e('Sorry, no posts matched your criteria.', 'blocks'); ?>
		</div>
	</div>

<?php endif; ?>

<?php get_footer(); ?>
5,029 次浏览 | 没有评论
2010年8月21日 | 归档于 技术

修正统计插件statpresscn的一个写UTF-8数据的错误

博客的访问统计插件使用的是statpresscn,从一开始使用163的api解析ip地址,就发现写入会出错,当时不以为意,随便改为其他的api(返回的地址一般都是空串)。直到昨天小烈留言,才发现原来用户名为中文字符同样也会出错。于是决心解决它。

WordPress database error: [Incorrect string value: ‘\xE7\x83\x88\xE4\xB9\x8B…’ for column ‘user’ at row 1]
INSERT INTO blog_wp_statpressCN (date, time, ip, urlrequested, statuscode, ptype, pvalue, agent, referrer, search,nation,os,browser,searchengine,spider,feed,user,timestamp) VALUES (‘20100820′,’00:25:24′,’125.46.23.194′,’/index.php/gbook’,”,’page’,’103′,’Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 (.NET CLR 3.5.30729)’,’https://kuyur.net/blog/index.php/archives/category/ongaku’,”,”,’Windows XP’,’Firefox 3′,”,”,”,’烈之斩’,’1282263924′)

从小烈反馈以及余自测的错误来看,是UTF-8字符要写入数据库时出错。访客在留言后,cookies自动记录用户名,访客再访问页面,statpresscn这时候采集的不再是ip,而是用户名。“\xE7\x83\x88\xE4\xB9\x8B”是“烈之”的UTF-8编码。余的博客是UTF-8编码,表单提交到mysql的字符类型也是UTF-8,但博文写得好好的,没理由是服务器那边的错误。将数据库进行备份,下载回来一看,发现表blog_wp_statpress的CHARSET居然是latin1。错误正是出在这里。(建议statpresscn作者在创建数据表时按照blog设定的编码创建)

修改表的字符集、user字段的CHARACTER SET为UTF-8,用中文用户名留言后的错误消失。将解析ip的api改回163,修改nation字段的CHARACTER SET为UTF-8,博文最开始提到的错误也消失。

以防万一,将tinytext和text类型的字段都改成了UTF-8。

ALTER TABLE blog_wp_statpress default CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify user tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify urlrequested text CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify agent text CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify referrer text CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify search text CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify nation tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify os tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify browser tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify searchengine tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify spider tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify feed tinytext CHARACTER SET utf8;
ALTER TABLE blog_wp_statpress modify ptype tinytext CHARACTER SET utf8;

可以将上述sql语句保存sql文件,在mysql中用source命令执行。

7,551 次浏览 | 1 条评论
2010年8月20日 | 归档于 技术
标签: ,

アカイイト 游戏脚本文件分析及脚本提取

アカイイト,中文译名红线,success社05年出品的PS2平台上的和风百合游戏。音乐由Little WING负责,神作。
这是官网:http://www.success-corp.co.jp/software/ps2/akaiito/

红线的脚本文件是PS2光盘或者镜像里的SCRIPT\SCRIPT.AFS文件。其余文件的内容凭文件名也很好猜。BGM.AFS是背景音效,CELDATA.AFS是CG,VOICE.AFS是语音。NULL.000和NULL.001则是两个垃圾文件,每个大小512M,倘若制作汉化镜像应当去掉。

SCRIPT.AFS中的脚本文字是按SHIFT-JIS编码以非加密方式存放
下载地址:http://unicue.googlecode.com/svn/trunk/Files/Akaiito/SCRIPT.AFS
下载回来后可以用notepad++打开,就能发现脚本是一段一段的嵌套在非脚本字符中,非脚本字符有很多是不能显示的ASCII控制字符,并有大量0x00也即终止符,因此刚开始时不能当作字符串来处理。

不过脚本字符和干扰字符所属区段不重合,因此非常适合编写程序来处理,差不多六个函数搞定。

第一步,剔除控制符等干扰字符。
将整个文件都当成SHIFT-JIS编码的文本进行检测,原理和GBK、Shift-JIS、BIG5编码检测算法提及的检测算法是非常像的,只不过这里的检测遇到异常字符不终止继续而已。
剔除策略是,遇到0x00~0x1F用空格替代;遇到0x7F,用空格替代;半角假名区的字符事实上没有出现(倘若余错了,请联系纠正);遇到0x8000~0x813F,用两个空格代替;遇到SHIFT-JIS中0x8140~0xFFFF中不存在的字符(使用到了映射表文件jis2u-little-endian.map进行检查),用两个空格取代。

在第一步后,文本已经是一个正经的字符串,这时候可以用正则表达式进行后续处理。

第二步,删除$w、$s、单个ASCII可见字符(前后均有空格),有限状态自动机模型。第二步后文本已经基本可读。

第三步,需要将文本分割成章节标题和脚本内容两部份分别处理。#cr0在游戏中是回车的转义符,章节标题字串的#cr0是替换为换行,脚本内容字串的则是删除掉(脚本内容字串的最后部分需要灵活处理)。最后的工作是将长空格串(三个连续空格以上)替换为换行。自写代码的处理到此结束。在去除空格串后文本由6M多缩小到1M多。

第四步是合并文本,并将SHIFT-JIS编码转换为UTF-8(使用ANSI2Unicode,本想调用toUnicode.h实现的),用notepad++的正则表达式替换功能删除数字编号(应该不会删错吧?自写程序会鉴别前后是否有换行。偷懒了),并将多余的回车换行删掉,最后剔除少量错误。

step3的脚本:http://u.115.com/file/f0f1b09c22,未删除数字的或许对分章有意义

程序:http://unicue.googlecode.com/svn/trunk/

最终脚本在此:akaiito_script_step4.pdf
共计57万字

10,506 次浏览 | 7 条评论
2010年8月20日 | 归档于 程序, 红线