分享
 
 
 

一个碎片整理程序的实现

王朝vc·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

申明: 代码是网上收集的,原则上只供学习使用

//====================================================================

//

// Defrag.h

//

// Copyright (C) 1997 Mark Russinovich

//

// Header file for defragmentation demonstration program. This file

// includes definitions for defragmentation File System Control

// commands, as well as the undocumented NtFsControl call.

//

//====================================================================

//--------------------------------------------------------------------

// D E F I N E S

//--------------------------------------------------------------------

//

// File System Control commands related to defragging

//

#define FSCTL_READ_MFT_RECORD 0x90068

#define FSCTL_GET_VOLUME_BITMAP 0x9006F

#define FSCTL_GET_RETRIEVAL_POINTERS 0x90073

#define FSCTL_MOVE_FILE 0x90074

//

// return code type

//

typedef UINT NTSTATUS;

//

// Error codes returned by NtFsControlFile (see NTSTATUS.H)

//

#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)

#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)

#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL)

#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)

#define STATUS_ALREADY_COMMITTED ((NTSTATUS)0xC0000021L)

#define STATUS_INVALID_DEVICE_REQUEST ((NTSTATUS)0xC0000010L)

//--------------------------------------------------------------------

// F S C T L S P E C I F I C T Y P E D E F S

//--------------------------------------------------------------------

//

// This is the definition for a VCN/LCN (virtual cluster/logical cluster)

// mapping pair that is returned in the buffer passed to

// FSCTL_GET_RETRIEVAL_POINTERS

//

typedef struct {

ULONGLONG Vcn;

ULONGLONG Lcn;

} MAPPING_PAIR, *PMAPPING_PAIR;

//

// This is the definition for the buffer that FSCTL_GET_RETRIEVAL_POINTERS

// returns. It consists of a header followed by mapping pairs

//

typedef struct {

ULONG NumberOfPairs;

ULONGLONG StartVcn;

MAPPING_PAIR Pair[1];

} GET_RETRIEVAL_DESCRIPTOR, *PGET_RETRIEVAL_DESCRIPTOR;

//

// This is the definition of the buffer that FSCTL_GET_VOLUME_BITMAP

// returns. It consists of a header followed by the actual bitmap data

//

typedef struct {

ULONGLONG StartLcn;

ULONGLONG ClustersToEndOfVol;

BYTE Map[1];

} BITMAP_DESCRIPTOR, *PBITMAP_DESCRIPTOR;

//

// This is the definition for the data structure that is passed in to

// FSCTL_MOVE_FILE

//

typedef struct {

HANDLE FileHandle;

ULONG Reserved;

LARGE_INTEGER StartVcn;

LARGE_INTEGER TargetLcn;

ULONG NumVcns;

ULONG Reserved1;

} MOVEFILE_DESCRIPTOR, *PMOVEFILE_DESCRIPTOR;

//--------------------------------------------------------------------

// N T F S C O N T R O L F I L E D E F I N I T I O N S

//--------------------------------------------------------------------

//

// Prototype for NtFsControlFile and data structures

// used in its definition

//

//

// Io Status block (see NTDDK.H)

//

typedef struct _IO_STATUS_BLOCK {

NTSTATUS Status;

ULONG Information;

} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

//

// Apc Routine (see NTDDK.H)

//

typedef VOID (*PIO_APC_ROUTINE) (

PVOID ApcContext,

PIO_STATUS_BLOCK IoStatusBlock,

ULONG Reserved

);

//

// The undocumented NtFsControlFile

//

// This function is used to send File System Control (FSCTL)

// commands into file system drivers. Its definition is

// in ntdll.dll (ntdll.lib), a file shipped with the NTDDK.

//

NTSTATUS (__stdcall *NtFsControlFile)(

HANDLE FileHandle,

HANDLE Event, // optional

PIO_APC_ROUTINE ApcRoutine, // optional

PVOID ApcContext, // optional

PIO_STATUS_BLOCK IoStatusBlock,

ULONG FsControlCode,

PVOID InputBuffer, // optional

ULONG InputBufferLength,

PVOID OutputBuffer, // optional

ULONG OutputBufferLength

);

//====================================================================

//

// Defrag.c

//

// Copyright (C) 1997 Mark Russinovich

//

// This program demonstrates the use of NT 4.0 FAT and NTFS cluster

// movement File System Control functions.

//

//====================================================================

#include <windows.h>

#include <stdio.h>

#include <conio.h>

#include "defrag.h"

//--------------------------------------------------------------------

// D E F I N E S

//--------------------------------------------------------------------

//

// Interval at which output is paused (in lines)

//

#define PAUSEINTERVAL 24

//

// Size of the buffer we read file mapping information into.

// The buffer is big enough to hold the 16 bytes that

// come back at the head of the buffer (the number of entries

// and the starting virtual cluster), as well as 512 pairs

// of [virtual cluster, logical cluster] pairs.

//

#define FILEMAPSIZE (512+2)

//

// Size of the bitmap buffer we pass in. Its large enough to

// hold information for the 16-byte header that's returned

// plus the indicated number of bytes, each of which has 8 bits

// (imagine that!)

//

#define BITMAPBYTES 4096

#define BITMAPSIZE (BITMAPBYTES+2*sizeof(ULONGLONG))

//

// Invalid longlong number

//

#define LLINVALID ((ULONGLONG) -1)

//--------------------------------------------------------------------

// G L O B A L S

//--------------------------------------------------------------------

//

// Handle for the raw volume that was opened

//

HANDLE VolumeHandle;

//

// Buffer to read file mapping information into

//

ULONGLONG FileMap[ FILEMAPSIZE ];

//

// Buffer thats passed to bitmap function

//

BYTE BitMap[ BITMAPSIZE ];

//

// Bit shifting array for efficient processing of the bitmap

//

BYTE BitShift[] = { 1, 2, 4, 8, 16, 32, 64, 128 };

//--------------------------------------------------------------------

// F U N C T I O N S

//--------------------------------------------------------------------

//--------------------------------------------------------------------

//

// PrintNtError

//

// Translates an NTDLL error code into its text equivalent. This

// only deals with ones commonly returned by defragmenting FS Control

// commands.

//--------------------------------------------------------------------

void PrintNtError( NTSTATUS Status )

{

switch( Status ) {

case STATUS_SUCCESS:

printf("STATUS_SUCCESS\n\n");

break;

case STATUS_INVALID_PARAMETER:

printf("STATUS_INVALID_PARAMETER\n\n");

break;

case STATUS_BUFFER_TOO_SMALL:

printf("STATUS_BUFFER_TOO_SMALL\n\n");

break;

case STATUS_ALREADY_COMMITTED:

printf("STATUS_ALREADY_COMMITTED\n\n");

break;

case STATUS_INVALID_DEVICE_REQUEST:

printf("STATUS_INVALID_DEVICE_REQUEST\n\n");

break;

default:

printf("0x%08x\n\n", Status );

break;

}

}

//--------------------------------------------------------------------

//

// PrintWin32Error

//

// Translates a Win32 error into a text equivalent

//

//--------------------------------------------------------------------

void PrintWin32Error( DWORD ErrorCode )

{

LPVOID lpMsgBuf;

FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,

NULL, ErrorCode,

MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),

(LPTSTR) &lpMsgBuf, 0, NULL );

printf("%s\n", lpMsgBuf );

LocalFree( lpMsgBuf );

}

//--------------------------------------------------------------------

//

// PrintHelp

//

//--------------------------------------------------------------------

void PrintHelp()

{

printf("\nCommands:\n\n");

printf("Dump bitmap free clusters:\n");

printf(" b [offset]\n");

printf("Enumerate clusters of file:\n");

printf(" n [filename]\n");

printf("Move clusters:\n");

printf(" m [file] [off] [tgt] [numclust]\n");

printf("Quit:\n");

printf(" q\n");

printf("\n");

}

//--------------------------------------------------------------------

//

// PauseOutput

//

// After n lines have printed, stop and wait for the user to continue.

// 'q' causes the function to return false.

//

//--------------------------------------------------------------------

BOOL PauseOutput( DWORD Count )

{

char key;

if( !(Count % PAUSEINTERVAL )) {

printf("more ('q' to quit): ");

fflush(stdout);

key = getch();

printf("\n");

if( key == 'q' ) {

printf("\nEnumeration aborted.\n\n");

return FALSE;

}

}

return TRUE;

}

//--------------------------------------------------------------------

//

// OpenVolume

//

// Open the volume for defragging, a flag that is new for NT 4.0.

//

//--------------------------------------------------------------------

DWORD OpenVolume( int DriveId )

{

static char volumeName[] = "\\\\.\\A:";

//

// open the volume

//

volumeName[4] = DriveId + 'A';

VolumeHandle = CreateFile( volumeName, GENERIC_READ,

FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING,

0, 0 );

if( VolumeHandle == INVALID_HANDLE_VALUE ) {

return GetLastError();

}

return ERROR_SUCCESS;

}

//--------------------------------------------------------------------

//

// DumpBitmap

//

// Start at the offset specified (if any) and dumps all the free

// clusters on the volume to the end of the volume or until

// the user stops the dump with a 'q'.

//

//--------------------------------------------------------------------

void DumpBitmap( char *argument )

{

DWORD status;

PBITMAP_DESCRIPTOR bitMappings;

ULONGLONG cluster;

ULONGLONG numFree;

ULONGLONG startLcn;

ULONGLONG nextLcn;

ULONGLONG lastLcn;

IO_STATUS_BLOCK ioStatus;

ULONGLONG i;

int lines;

//

// Start scanning at the cluster offset the user specifies

//

bitMappings = (PBITMAP_DESCRIPTOR) BitMap;

cluster = 0;

nextLcn = 0;

lines = 0;

lastLcn = LLINVALID;

sscanf( argument, " %I64d ", &nextLcn );

while( !(status = NtFsControlFile( VolumeHandle, NULL, NULL, 0, &ioStatus,

FSCTL_GET_VOLUME_BITMAP,

&nextLcn, sizeof( cluster ),

bitMappings, BITMAPSIZE )) ||

status == STATUS_BUFFER_OVERFLOW ||

status == STATUS_PENDING ) {

//

// If the operation is pending, wait for it to finish

//

if( status == STATUS_PENDING ) {

WaitForSingleObject( VolumeHandle, INFINITE );

//

// Get the status from the status block

//

if( ioStatus.Status != STATUS_SUCCESS &&

ioStatus.Status != STATUS_BUFFER_OVERFLOW ) {

printf("\nGet Volume Bitmap: ");

PrintNtError( ioStatus.Status );

return;

}

}

//

// Print the range we're starting at

//

if( !lines ) {

printf("\nFree clusters starting at offset: %I64d\n",

bitMappings->StartLcn );

}

//

// Scan through the returned bitmap info, looking for empty clusters

//

startLcn = bitMappings->StartLcn;

numFree = 0;

cluster = LLINVALID;

for( i = 0; i < min( bitMappings->ClustersToEndOfVol, 8*BITMAPBYTES); i++ ) {

if( !(bitMappings->Map[ i/8 ] & BitShift[ i % 8 ])) {

//

// Cluster is free

//

if( cluster == LLINVALID ) {

cluster = startLcn + i;

numFree = 1;

} else {

numFree++;

}

} else {

//

// Cluster is not free

//

if( cluster != LLINVALID ) {

if( lastLcn == cluster ) {

lastLcn = LLINVALID;

} else {

//

// See if we should continue

//

if( !PauseOutput( ++lines ) ) {

return;

}

printf(" LCN: %I64d LEN: %I64d\n", cluster, numFree );

numFree = 0;

lastLcn = cluster;

cluster = LLINVALID;

}

}

}

}

//

// See if we should continue

//

if( !PauseOutput( ++lines ) ) {

return;

}

//

// Print any remaining

//

if( cluster != LLINVALID && lastLcn != cluster ) {

printf(" LCN: %I64d LEN: %I64d\n", cluster, numFree );

numFree = 0;

cluster = LLINVALID;

}

//

// End of volume?

//

if( status != STATUS_BUFFER_OVERFLOW ) {

printf("End of volume.\n\n");

return;

}

//

// Move to the next block

//

nextLcn = bitMappings->StartLcn + i;

}

//

// We only get here when there's an error

//

printf("\nGet Volume Bitmap: ");

PrintNtError( status );

}

//--------------------------------------------------------------------

//

// DumpFile

//

// Dumps the clusters belonging to the specified file until the

// end of the file or the user stops the dump.

//

//--------------------------------------------------------------------

void DumpFile( int drive, char *argument )

{

DWORD status;

int i;

HANDLE sourceFile;

char fileName[MAX_PATH];

IO_STATUS_BLOCK ioStatus;

ULONGLONG startVcn;

PGET_RETRIEVAL_DESCRIPTOR fileMappings;

int lines = 0;

//

// Make the name into a real pathname

//

if( strlen( argument ) > 1 && argument[0] != '\\' &&

argument[0] != 'A'+drive &&

argument[0] != 'a'+drive )

sprintf(fileName, "%C:\\%s", drive+'A', argument );

else if( strlen( argument ) > 1 && argument[0] == '\\')

sprintf(fileName, "%C:%s", drive+'A', argument );

else

strcpy(fileName, argument );

printf("\nClusters for file: %s\n", fileName );

//

// Open the file

//

sourceFile = CreateFile( fileName, GENERIC_READ,

FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING,

FILE_FLAG_NO_BUFFERING, 0 );

if( sourceFile == INVALID_HANDLE_VALUE ) {

printf("Failed to open file: ");

PrintWin32Error( GetLastError() );

return;

}

//

// Start dumping the mapping information. Go until we hit the end of the

// file.

//

startVcn = 0;

fileMappings = (PGET_RETRIEVAL_DESCRIPTOR) FileMap;

while( !(status = NtFsControlFile( sourceFile, NULL, NULL, 0, &ioStatus,

FSCTL_GET_RETRIEVAL_POINTERS,

&startVcn, sizeof( startVcn ),

fileMappings, FILEMAPSIZE * sizeof(LARGE_INTEGER) ) ) ||

status == STATUS_BUFFER_OVERFLOW ||

status == STATUS_PENDING ) {

//

// If the operation is pending, wait for it to finish

//

if( status == STATUS_PENDING ) {

WaitForSingleObject( sourceFile, INFINITE );

//

// Get the status from the status block

//

if( ioStatus.Status != STATUS_SUCCESS &&

ioStatus.Status != STATUS_BUFFER_OVERFLOW ) {

printf("Enumerate file clusters: ");

PrintNtError( ioStatus.Status );

return;

}

}

//

// Loop through the buffer of number/cluster pairs, printing them

// out.

//

startVcn = fileMappings->StartVcn;

for( i = 0; i < (ULONGLONG) fileMappings->NumberOfPairs; i++ ) {

//

// See if we should continue

//

if( !PauseOutput( ++lines ) ) {

return;

}

//

// On NT 4.0, a compressed virtual run (0-filled) is

// identified with a cluster offset of -1

//

if( fileMappings->Pair[i].Lcn == LLINVALID ) {

printf(" VCN: %I64d VIRTUAL LEN: %I64d\n",

startVcn, fileMappings->Pair[i].Vcn - startVcn );

} else {

printf(" VCN: %I64d LCN: %I64d LEN: %I64d\n",

startVcn, fileMappings->Pair[i].Lcn,

fileMappings->Pair[i].Vcn - startVcn );

}

startVcn = fileMappings->Pair[i].Vcn;

}

//

// If the buffer wasn't overflowed, then we're done

//

if( !status )

break;

}

CloseHandle( sourceFile );

//

// Print any error code

//

printf("Enumerate file clusters: ");

PrintNtError( status );

}

//--------------------------------------------------------------------

//

// MoveClusterUsage

//

// Prints the syntax of the demonstration program's move file command.

//

//--------------------------------------------------------------------

void MoveClusterUsage()

{

printf("\nMove File's syntax is:\n m [filename] [fileoffset] [target] [numclusters]\n\n");

printf("Example:\n m c:\\foo\\bar 5 3455 10\n");

printf(" c:\\foo\\bar File to move\n");

printf(" 5 Start offset (in clusters) of the cluster in file to move\n");

printf(" 3455 Target cluster on drive\n");

printf(" 10 Number of clusters to move\n");

printf("\n This would direct 10 clusters, starting at offset 5 clusters\n"

" in the file, to be moved to logical cluster 3455 on the volume.\n\n");

return;

}

//--------------------------------------------------------------------

//

// MoveCluster

//

// This uses the FSCT_MOVE_FILE interface to move the clusters of a

// file specified by the user as arguments. MoveFile requires a

// file handle, an offset within the file, the number of sectors of

// the file to move, and the target cluster on the drive to move the

// clusters to.

//

//--------------------------------------------------------------------

void MoveCluster( int drive, char *argument )

{

DWORD status;

IO_STATUS_BLOCK ioStatus;

char *argptr;

HANDLE sourceFile;

char fileName[MAX_PATH];

LARGE_INTEGER startVcn, targetLcn;

DWORD numClusters;

MOVEFILE_DESCRIPTOR moveFile;

//

// First, we have to extract the file name

//

argptr = argument;

while( *argptr && *argptr != ' ' ) argptr++;

if( !*argptr ) {

MoveClusterUsage();

return;

}

//

// Make the name into a real pathname

//

*argptr = 0;

if( strlen( argument ) > 1 && argument[0] != '\\' &&

argument[0] != 'A'+drive &&

argument[0] != 'a'+drive )

sprintf(fileName, "%C:\\%s", drive+'A', argument );

else if( strlen( argument ) > 1 && argument[0] == '\\')

sprintf(fileName, "%C:%s", drive+'A', argument );

else

strcpy(fileName, argument );

//

// Get numeric parameters

//

argument = argptr+1;

if( sscanf( argument, " %I64d %I64d %d ", &startVcn, &targetLcn, &numClusters ) != 3) {

MoveClusterUsage();

return;

}

//

// Tell user what we're going to try

//

printf("\nMoving file %s:\n", fileName );

printf(" Start Offset: %I64d\n", startVcn );

printf(" Number of Clusters: %d\n", numClusters );

printf(" Target Cluster: %I64d\n", targetLcn );

//

// Open the file

//

sourceFile = CreateFile( fileName, GENERIC_READ,

FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING,

FILE_FLAG_NO_BUFFERING, 0 );

if( sourceFile == INVALID_HANDLE_VALUE ) {

printf("Failed to open file: ");

PrintWin32Error( GetLastError() );

return;

}

//

// Setup movefile descriptor and make the call

//

moveFile.FileHandle = sourceFile;

moveFile.StartVcn = startVcn;

moveFile.TargetLcn = targetLcn;

moveFile.NumVcns = numClusters;

status = NtFsControlFile( VolumeHandle, NULL, NULL, 0, &ioStatus,

FSCTL_MOVE_FILE,

&moveFile, sizeof( moveFile ),

NULL, 0 );

//

// If the operation is pending, wait for it to finish

//

if( status == STATUS_PENDING ) {

WaitForSingleObject( sourceFile, INFINITE );

status = ioStatus.Status;

}

//

// Print status

//

printf("Move cluster status: ");

PrintNtError( status );

}

//--------------------------------------------------------------------

//

// ExtractCommand

//

// Given a command line, searches for 1 character command, and then

// returns a pointer to first non-whitespace following.

//

//--------------------------------------------------------------------

char ExtractCommand( char *command, char **argument )

{

char cmdChar;

//

// Look for the command character

//

while( *command && *command == ' ') command++;

if( !*command) return (char) 0;

cmdChar = *command;

command++;

//

// Now look for argument

//

while( *command && *command == ' ' ) command++;

*argument = command;

return cmdChar;

}

//--------------------------------------------------------------------

//

// main

//

// Process simple commands for enumerating the clusters of a file,

// reading the volume bitmap, and moving a cluster of a particular

// file.

//

//--------------------------------------------------------------------

int main( int argc, char *argv[])

{

DWORD status;

int drive;

char command[256];

char *argument;

char cmdChar;

//

// Get the drive to open off the command line

//

if( argc != 2) {

printf("Usage: %s <drive letter>\n", argv[0] );

exit(1);

}

printf("\nNT 4.0 Defragmentation Demonstration Program V1.0\n");

printf("Copyright (C) 1997 Mark Russinovich\n");

printf("http://www.ntinternals.com\n\n");

if( argv[1][0] >= 'a' && argv[1][0] <= 'z' ) {

drive = argv[1][0] - 'a';

} else if( argv[1][0] >= 'A' && argv[1][0] <= 'Z' ) {

drive = argv[1][0] - 'A';

} else if( argv[1][0] == '/' ) {

printf("Usage: %s <drive letter>\n", argv[0] );

exit(1);

} else {

printf("illegal drive: %c\n", argv[1][0] );

exit(1);

}

//

// Get the NtFsControlFile entry point

//

if( !(NtFsControlFile = (void *) GetProcAddress( GetModuleHandle("ntdll.dll"),

"NtFsControlFile" )) ) {

printf("Could not find NtFsControlFile entry point in NTDLL.DLL\n");

exit(1);

}

//

// Open the volume

//

printf("Opening volume: %c\n", drive+'A' );

status = OpenVolume(drive);

printf("Open status: ");

PrintWin32Error( status );

if( status != ERROR_SUCCESS ) {

printf("Exiting.\n");

exit(0);

}

//

// Get commands

//

printf("Enter commands ('?' for help):\n\n");

while(1) {

printf(": ");

fflush(stdout );

gets( command );

cmdChar = ExtractCommand( command, &argument );

switch( cmdChar ) {

//

// Dump bitmap information

//

case 'b':

case 'B':

DumpBitmap( argument );

break;

//

// Help

//

case '?':

case 'H':

case 'h':

PrintHelp();

break;

//

// Move Cluster

//

case 'm':

case 'M':

MoveCluster( drive, argument );

break;

//

// Get cluster map for file specified by name

//

case 'N':

case 'n':

DumpFile( drive, argument );

break;

//

// Quit

//

case 'Q':

case 'q':

printf("\nQuiting\n");

exit(0);

break;

case 0:

break;

default:

printf("\nInvalid command\n\n");

break;

}

}

return 0;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有