Last active
September 30, 2024 00:51
-
-
Save kengonakajima/6c57fa3cfc1b5e3149613d370e125726 to your computer and use it in GitHub Desktop.
parse minecraft MCA files and any NBT data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# 出力先のテキストファイル名 | |
output_file="inhabited_times.txt" | |
# 既存の出力ファイルがあれば削除 | |
rm -f "$output_file" | |
# 現在のディレクトリ内のすべての.mcaファイルを処理 | |
for file in *.mca; do | |
echo "Processing $file..." | |
# 指定されたコマンドを実行し、最大のInhabitedTimeを取得 | |
max_inhabited_time=$(node parse.js "$file" | grep Inhabi | sed -E 's/.*"InhabitedTime": "([0-9]+)".*/\1/' | sort -n | tail -1) | |
# InhabitedTimeが取得できなかった場合の処理 | |
if [ -z "$max_inhabited_time" ]; then | |
echo "$file N/A" >> "$output_file" | |
else | |
echo "$file $max_inhabited_time" >> "$output_file" | |
fi | |
done | |
echo "結果が $output_file に保存されました。" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require('fs'); | |
const zlib = require('zlib'); | |
function parseMCA(filename) { | |
const buffer = fs.readFileSync(filename); | |
const sectors = []; | |
const timestamps = []; | |
// ロケーションテーブルを読み取る | |
for (let i = 0; i < 1024; i++) { | |
const offset = buffer.readUIntBE(i * 4, 3); | |
const sectorCount = buffer.readUInt8(i * 4 + 3); | |
sectors.push({ offset, sectorCount }); | |
} | |
// タイムスタンプテーブルを読み取る | |
for (let i = 0; i < 1024; i++) { | |
const timestamp = buffer.readUInt32BE(4096 + i * 4); | |
timestamps.push(timestamp); | |
} | |
const chunks = []; | |
for (let i = 0; i < 1024; i++) { | |
const sector = sectors[i]; | |
if (sector.offset === 0 || sector.sectorCount === 0) { | |
continue; // 空のチャンク | |
} | |
const chunkOffset = sector.offset * 4096; | |
const length = buffer.readUInt32BE(chunkOffset); | |
const chunkData = buffer.slice(chunkOffset + 4, chunkOffset + 4 + length); | |
const compressionType = chunkData.readUInt8(0); | |
const compressedData = chunkData.slice(1); | |
let data; | |
if (compressionType === 2) { // zlib圧縮 | |
data = zlib.inflateSync(compressedData); | |
} else if (compressionType === 1) { // GZip圧縮 | |
data = zlib.unzipSync(compressedData); | |
} else { | |
console.error(`未知の圧縮タイプ ${compressionType} at chunk ${i}`); | |
continue; | |
} | |
// NBTデータをパースする | |
const nbtData = parseNBT(data); | |
chunks.push({ | |
index: i, | |
xPos: i % 32, | |
zPos: Math.floor(i / 32), | |
timestamp: timestamps[i], | |
data: nbtData | |
}); | |
} | |
return chunks; | |
} | |
function parseNBT(buffer) { | |
let offset = 0; | |
function readTag() { | |
if (offset >= buffer.length) { | |
return null; | |
} | |
const tagType = buffer.readUInt8(offset++); | |
if (tagType === 0) { // TAG_End | |
return null; | |
} | |
// タグの名前を読み取る | |
const nameLength = buffer.readUInt16BE(offset); | |
offset += 2; | |
const name = buffer.toString('utf8', offset, offset + nameLength); | |
offset += nameLength; | |
const value = readPayload(tagType); | |
return { | |
type: getTagTypeName(tagType), | |
name: name, | |
value: value | |
}; | |
} | |
function readPayload(tagType) { | |
switch (tagType) { | |
case 1: // TAG_Byte | |
return buffer.readInt8(offset++); | |
case 2: // TAG_Short | |
const short = buffer.readInt16BE(offset); | |
offset += 2; | |
return short; | |
case 3: // TAG_Int | |
const int = buffer.readInt32BE(offset); | |
offset += 4; | |
return int; | |
case 4: // TAG_Long | |
const high = buffer.readInt32BE(offset); | |
const low = buffer.readUInt32BE(offset + 4); | |
offset += 8; | |
return BigInt(high) << 32n | BigInt(low); | |
case 5: // TAG_Float | |
const float = buffer.readFloatBE(offset); | |
offset += 4; | |
return float; | |
case 6: // TAG_Double | |
const double = buffer.readDoubleBE(offset); | |
offset += 8; | |
return double; | |
case 7: // TAG_Byte_Array | |
const length = buffer.readInt32BE(offset); | |
offset += 4; | |
const byteArray = buffer.slice(offset, offset + length); | |
offset += length; | |
return Array.from(byteArray); | |
case 8: // TAG_String | |
const strLength = buffer.readUInt16BE(offset); | |
offset += 2; | |
const str = buffer.toString('utf8', offset, offset + strLength); | |
offset += strLength; | |
return str; | |
case 9: // TAG_List | |
const itemType = buffer.readUInt8(offset++); | |
const listLength = buffer.readInt32BE(offset); | |
offset += 4; | |
const list = []; | |
for (let i = 0; i < listLength; i++) { | |
list.push(readPayload(itemType)); | |
} | |
return list; | |
case 10: // TAG_Compound | |
const compound = {}; | |
while (true) { | |
const tag = readTag(); | |
if (tag === null) { // TAG_End | |
break; | |
} | |
compound[tag.name] = tag.value; | |
} | |
return compound; | |
case 11: // TAG_Int_Array | |
const intArrayLength = buffer.readInt32BE(offset); | |
offset += 4; | |
const intArray = []; | |
for (let i = 0; i < intArrayLength; i++) { | |
intArray.push(buffer.readInt32BE(offset)); | |
offset += 4; | |
} | |
return intArray; | |
case 12: // TAG_Long_Array | |
const longArrayLength = buffer.readInt32BE(offset); | |
offset += 4; | |
const longArray = []; | |
for (let i = 0; i < longArrayLength; i++) { | |
const high = buffer.readInt32BE(offset); | |
const low = buffer.readUInt32BE(offset + 4); | |
offset += 8; | |
longArray.push(BigInt(high) << 32n | BigInt(low)); | |
} | |
return longArray; | |
default: | |
throw new Error(`未知のタグタイプ: ${tagType}`); | |
} | |
} | |
function getTagTypeName(tagType) { | |
switch (tagType) { | |
case 0: return 'TAG_End'; | |
case 1: return 'TAG_Byte'; | |
case 2: return 'TAG_Short'; | |
case 3: return 'TAG_Int'; | |
case 4: return 'TAG_Long'; | |
case 5: return 'TAG_Float'; | |
case 6: return 'TAG_Double'; | |
case 7: return 'TAG_Byte_Array'; | |
case 8: return 'TAG_String'; | |
case 9: return 'TAG_List'; | |
case 10: return 'TAG_Compound'; | |
case 11: return 'TAG_Int_Array'; | |
case 12: return 'TAG_Long_Array'; | |
default: return 'Unknown'; | |
} | |
} | |
const rootTag = readTag(); | |
return rootTag; | |
} | |
function jsonStringifyWithBigInt(obj) { | |
return JSON.stringify(obj, (key, value) => | |
typeof value === 'bigint' ? value.toString() : value | |
, 2); | |
} | |
// 使用例 | |
const filename = 'r.0.0.mca'; // 解析したいMCAファイルのパスを指定 | |
const chunks = parseMCA(filename); | |
// JSONに変換 | |
const json = jsonStringifyWithBigInt(chunks); | |
// ファイルに保存 | |
fs.writeFileSync('output.json', json); | |
console.log('MCAファイルを解析し、output.jsonに保存しました。'); |
node parse.js r.1.-1.mca| grep Inhabi | sed -E 's/.*"InhabitedTime": "([0-9]+)".*/\1/' |sort -n|tail -1
all.sh scans all files in current directory and parse them and print inhabited time .
sort -k2,2 -n inhabited_times.txt | tail -400| awk '{print $1}'
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is written by ChatGPT.