package com.sneed.pkrandom.newnds;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import com.sneed.pkrandom.SysConstants;
import com.sneed.pkrandom.FileFunctions;
import com.sneed.pkrandom.RomFunctions;
import com.sneed.pkrandom.exceptions.CannotWriteToLocationException;
import com.sneed.pkrandom.exceptions.RandomizerIOException;
import cuecompressors.BLZCoder;
/*----------------------------------------------------------------------------*/
/*-- NDSRom.java - base class for opening/saving ROMs --*/
/*-- Code based on "Nintendo DS rom tool", copyright (C) DevkitPro --*/
/*-- Original Code by Rafael Vuijk, Dave Murphy, Alexei Karpenko --*/
/*-- --*/
/*-- Ported to Java by sneed under the terms of the GPL: --*/
/*-- --*/
/*-- 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 3 of the License, or --*/
/*-- (at your option) any later version. --*/
/*-- --*/
/*-- 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. --*/
/*-- --*/
/*-- You should have received a copy of the GNU General Public License --*/
/*-- along with this program. If not, see . --*/
/*----------------------------------------------------------------------------*/
public class NDSRom {
private String romCode;
private byte version;
private String romFilename;
private RandomAccessFile baseRom;
private boolean romOpen;
private Map files;
private Map filesByID;
private Map arm9overlaysByFileID;
private NDSY9Entry[] arm9overlays;
private byte[] fat;
private String tmpFolder;
private boolean writingEnabled;
private boolean arm9_open, arm9_changed, arm9_has_footer;
private boolean arm9_compressed;
private int arm9_ramoffset;
private int arm9_szoffset;
private byte[] arm9_footer;
private byte[] arm9_ramstored;
private long originalArm9CRC;
private static final int arm9_align = 0x1FF, arm7_align = 0x1FF;
private static final int fnt_align = 0x1FF, fat_align = 0x1FF;
private static final int banner_align = 0x1FF, file_align = 0x1FF;
public NDSRom(String filename) throws IOException {
this.romFilename = filename;
this.baseRom = new RandomAccessFile(filename, "r");
this.romOpen = true;
// TMP folder?
String rawFilename = new File(filename).getName();
String dataFolder = "tmp_" + rawFilename.substring(0, rawFilename.lastIndexOf('.'));
// remove nonsensical chars
dataFolder = dataFolder.replaceAll("[^A-Za-z0-9_]+", "");
File tmpFolder = new File(SysConstants.ROOT_PATH + dataFolder);
tmpFolder.mkdir();
if (tmpFolder.canWrite()) {
writingEnabled = true;
this.tmpFolder = SysConstants.ROOT_PATH + dataFolder + File.separator;
tmpFolder.deleteOnExit();
} else {
writingEnabled = false;
}
readFileSystem();
arm9_open = false;
arm9_changed = false;
arm9_ramstored = null;
}
public void reopenROM() throws IOException {
if (!this.romOpen) {
this.baseRom = new RandomAccessFile(this.romFilename, "r");
this.romOpen = true;
}
}
public void closeROM() throws IOException {
if (this.romOpen && this.baseRom != null) {
this.baseRom.close();
this.baseRom = null;
this.romOpen = false;
}
}
private void readFileSystem() throws IOException {
// read rom code
baseRom.seek(0x0C);
byte[] sig = new byte[4];
baseRom.readFully(sig);
this.romCode = new String(sig, "US-ASCII");
baseRom.seek(0x1E);
this.version = baseRom.readByte();
baseRom.seek(0x28);
this.arm9_ramoffset = readFromFile(baseRom, 4);
baseRom.seek(0x40);
int fntOffset = readFromFile(baseRom, 4);
readFromFile(baseRom, 4); // fntSize not needed
int fatOffset = readFromFile(baseRom, 4);
int fatSize = readFromFile(baseRom, 4);
// Read full FAT table
baseRom.seek(fatOffset);
fat = new byte[fatSize];
baseRom.readFully(fat);
Map directoryPaths = new HashMap<>();
directoryPaths.put(0xF000, "");
int dircount = readFromFile(baseRom, fntOffset + 0x6, 2);
files = new HashMap<>();
filesByID = new HashMap<>();
// read fnt table
baseRom.seek(fntOffset);
int[] subTableOffsets = new int[dircount];
int[] firstFileIDs = new int[dircount];
int[] parentDirIDs = new int[dircount];
for (int i = 0; i < dircount && i < 0x1000; i++) {
subTableOffsets[i] = readFromFile(baseRom, 4) + fntOffset;
firstFileIDs[i] = readFromFile(baseRom, 2);
parentDirIDs[i] = readFromFile(baseRom, 2);
}
// get dirnames
String[] directoryNames = new String[dircount];
Map filenames = new TreeMap<>();
Map fileDirectories = new HashMap<>();
for (int i = 0; i < dircount && i < 0x1000; i++) {
firstPassDirectory(i, subTableOffsets[i], firstFileIDs[i], directoryNames, filenames, fileDirectories);
}
// get full dirnames
for (int i = 1; i < dircount && i < 0x1000; i++) {
String dirname = directoryNames[i];
if (dirname != null) {
StringBuilder fullDirName = new StringBuilder();
int curDir = i;
while (dirname != null && !dirname.isEmpty()) {
if (fullDirName.length() > 0) {
fullDirName.insert(0, "/");
}
fullDirName.insert(0, dirname);
int parentDir = parentDirIDs[curDir];
if (parentDir >= 0xF001 && parentDir <= 0xFFFF) {
curDir = parentDir - 0xF000;
dirname = directoryNames[curDir];
} else {
break;
}
}
directoryPaths.put(i + 0xF000, fullDirName.toString());
} else {
directoryPaths.put(i + 0xF000, "");
}
}
// parse files
for (int fileID : filenames.keySet()) {
String filename = filenames.get(fileID);
int directory = fileDirectories.get(fileID);
String dirPath = directoryPaths.get(directory + 0xF000);
String fullFilename = filename;
if (!dirPath.isEmpty()) {
fullFilename = dirPath + "/" + filename;
}
NDSFile nf = new NDSFile(this);
int start = readFromByteArr(fat, fileID * 8, 4);
int end = readFromByteArr(fat, fileID * 8 + 4, 4);
nf.offset = start;
nf.size = end - start;
nf.fullPath = fullFilename;
nf.fileID = fileID;
files.put(fullFilename, nf);
filesByID.put(fileID, nf);
}
// arm9 overlays
int arm9_ovl_table_offset = readFromFile(baseRom, 0x50, 4);
int arm9_ovl_table_size = readFromFile(baseRom, 0x54, 4);
int arm9_ovl_count = arm9_ovl_table_size / 32;
byte[] y9table = new byte[arm9_ovl_table_size];
arm9overlays = new NDSY9Entry[arm9_ovl_count];
arm9overlaysByFileID = new HashMap<>();
baseRom.seek(arm9_ovl_table_offset);
baseRom.readFully(y9table);
// parse overlays
for (int i = 0; i < arm9_ovl_count; i++) {
NDSY9Entry overlay = new NDSY9Entry(this);
int fileID = readFromByteArr(y9table, i * 32 + 24, 4);
int start = readFromByteArr(fat, fileID * 8, 4);
int end = readFromByteArr(fat, fileID * 8 + 4, 4);
overlay.offset = start;
overlay.size = end - start;
overlay.original_size = end - start;
overlay.fileID = fileID;
overlay.overlay_id = i;
overlay.ram_address = readFromByteArr(y9table, i * 32 + 4, 4);
overlay.ram_size = readFromByteArr(y9table, i * 32 + 8, 4);
overlay.bss_size = readFromByteArr(y9table, i * 32 + 12, 4);
overlay.static_start = readFromByteArr(y9table, i * 32 + 16, 4);
overlay.static_end = readFromByteArr(y9table, i * 32 + 20, 4);
overlay.compressed_size = readFromByteArr(y9table, i * 32 + 28, 3);
overlay.compress_flag = y9table[i * 32 + 31] & 0xFF;
arm9overlays[i] = overlay;
arm9overlaysByFileID.put(fileID, overlay);
}
}
public void saveTo(String filename) throws IOException {
this.reopenROM();
// Initialize new ROM
RandomAccessFile fNew = new RandomAccessFile(filename, "rw");
int headersize = readFromFile(this.baseRom, 0x84, 4);
this.baseRom.seek(0);
copy(this.baseRom, fNew, headersize);
// arm9
int arm9_offset = ((int) (fNew.getFilePointer() + arm9_align)) & (~arm9_align);
int old_arm9_offset = readFromFile(this.baseRom, 0x20, 4);
int arm9_size = readFromFile(this.baseRom, 0x2C, 4);
if (arm9_open && arm9_changed) {
// custom arm9
byte[] newARM9 = getARM9();
if (arm9_compressed) {
newARM9 = new BLZCoder(null).BLZ_EncodePub(newARM9, true, false, "arm9.bin");
if (arm9_szoffset > 0) {
int newValue = newARM9.length + arm9_ramoffset;
writeToByteArr(newARM9, arm9_szoffset, 4, newValue);
}
}
arm9_size = newARM9.length;
// copy new arm9
fNew.seek(arm9_offset);
fNew.write(newARM9);
// footer?
if (arm9_has_footer) {
fNew.write(arm9_footer);
}
} else {
// copy arm9+footer
this.baseRom.seek(old_arm9_offset);
fNew.seek(arm9_offset);
copy(this.baseRom, fNew, arm9_size + 12);
}
// arm9 ovl
int arm9_ovl_offset = (int) fNew.getFilePointer();
int arm9_ovl_size = arm9overlays.length * 32;
// don't actually write arm9 ovl yet
// arm7
int arm7_offset = arm9_ovl_offset + arm9_ovl_size + arm7_align & (~arm7_align);
int old_arm7_offset = readFromFile(this.baseRom, 0x30, 4);
int arm7_size = readFromFile(this.baseRom, 0x3C, 4);
// copy arm7
this.baseRom.seek(old_arm7_offset);
fNew.seek(arm7_offset);
copy(this.baseRom, fNew, arm7_size);
// arm7 ovl
int arm7_ovl_offset = (int) fNew.getFilePointer();
int old_arm7_ovl_offset = readFromFile(this.baseRom, 0x58, 4);
int arm7_ovl_size = readFromFile(this.baseRom, 0x5C, 4);
// copy arm7 ovl
this.baseRom.seek(old_arm7_ovl_offset);
fNew.seek(arm7_ovl_offset);
copy(this.baseRom, fNew, arm7_ovl_size);
// banner
int banner_offset = ((int) (fNew.getFilePointer() + banner_align)) & (~banner_align);
int old_banner_offset = readFromFile(this.baseRom, 0x68, 4);
int banner_size = 0x840;
// copy banner
this.baseRom.seek(old_banner_offset);
fNew.seek(banner_offset);
copy(this.baseRom, fNew, banner_size);
// filename table (doesn't change)
int fnt_offset = ((int) (fNew.getFilePointer() + fnt_align)) & (~fnt_align);
int old_fnt_offset = readFromFile(this.baseRom, 0x40, 4);
int fnt_size = readFromFile(this.baseRom, 0x44, 4);
// copy fnt
this.baseRom.seek(old_fnt_offset);
fNew.seek(fnt_offset);
copy(this.baseRom, fNew, fnt_size);
// make space for the FAT table
int fat_offset = ((int) (fNew.getFilePointer() + fat_align)) & (~fat_align);
int fat_size = fat.length;
// Now for actual files
// Make a new FAT as needed
// also make a new y9 table
byte[] newfat = new byte[fat.length];
byte[] y9table = new byte[arm9overlays.length * 32];
int base_offset = fat_offset + fat_size;
int filecount = fat.length / 8;
for (int fid = 0; fid < filecount; fid++) {
int offset_of_file = (base_offset + file_align) & (~file_align);
int file_len = 0;
boolean copiedCustom = false;
if (filesByID.containsKey(fid)) {
byte[] customContents = filesByID.get(fid).getOverrideContents();
if (customContents != null) {
// copy custom
fNew.seek(offset_of_file);
fNew.write(customContents);
copiedCustom = true;
file_len = customContents.length;
}
}
if (arm9overlaysByFileID.containsKey(fid)) {
NDSY9Entry entry = arm9overlaysByFileID.get(fid);
int overlay_id = entry.overlay_id;
byte[] customContents = entry.getOverrideContents();
if (customContents != null) {
// copy custom
fNew.seek(offset_of_file);
fNew.write(customContents);
copiedCustom = true;
file_len = customContents.length;
}
// regardless, fill in y9 table
writeToByteArr(y9table, overlay_id * 32, 4, overlay_id);
writeToByteArr(y9table, overlay_id * 32 + 4, 4, entry.ram_address);
writeToByteArr(y9table, overlay_id * 32 + 8, 4, entry.ram_size);
writeToByteArr(y9table, overlay_id * 32 + 12, 4, entry.bss_size);
writeToByteArr(y9table, overlay_id * 32 + 16, 4, entry.static_start);
writeToByteArr(y9table, overlay_id * 32 + 20, 4, entry.static_end);
writeToByteArr(y9table, overlay_id * 32 + 24, 4, fid);
writeToByteArr(y9table, overlay_id * 32 + 28, 3, entry.compressed_size);
writeToByteArr(y9table, overlay_id * 32 + 31, 1, entry.compress_flag);
}
if (!copiedCustom) {
// copy from original ROM
int file_starts = readFromByteArr(fat, fid * 8, 4);
int file_ends = readFromByteArr(fat, fid * 8 + 4, 4);
file_len = file_ends - file_starts;
this.baseRom.seek(file_starts);
fNew.seek(offset_of_file);
copy(this.baseRom, fNew, file_len);
}
// write to new FAT
writeToByteArr(newfat, fid * 8, 4, offset_of_file);
writeToByteArr(newfat, fid * 8 + 4, 4, offset_of_file + file_len);
// update base_offset
base_offset = offset_of_file + file_len;
}
// write new FAT table
fNew.seek(fat_offset);
fNew.write(newfat);
// write y9 table
fNew.seek(arm9_ovl_offset);
fNew.write(y9table);
// tidy up ending
// base_offset is the end of the last file
int newfilesize = base_offset;
newfilesize = (newfilesize + 3) & ~3;
int application_end_offset = newfilesize;
if (newfilesize != base_offset) {
fNew.seek(newfilesize - 1);
fNew.write(0);
}
// calculate device capacity;
newfilesize |= newfilesize >> 16;
newfilesize |= newfilesize >> 8;
newfilesize |= newfilesize >> 4;
newfilesize |= newfilesize >> 2;
newfilesize |= newfilesize >> 1;
newfilesize++;
if (newfilesize <= 128 * 1024) {
newfilesize = 128 * 1024;
}
int devcap = -18;
int x = newfilesize;
while (x != 0) {
x >>= 1;
devcap++;
}
int devicecap = ((devcap < 0) ? 0 : devcap);
// Update offsets in ROM header
writeToFile(fNew, 0x20, 4, arm9_offset);
writeToFile(fNew, 0x2C, 4, arm9_size);
writeToFile(fNew, 0x30, 4, arm7_offset);
writeToFile(fNew, 0x3C, 4, arm7_size);
writeToFile(fNew, 0x40, 4, fnt_offset);
writeToFile(fNew, 0x48, 4, fat_offset);
writeToFile(fNew, 0x50, 4, arm9_ovl_offset);
writeToFile(fNew, 0x58, 4, arm7_ovl_offset);
writeToFile(fNew, 0x68, 4, banner_offset);
writeToFile(fNew, 0x80, 4, application_end_offset);
writeToFile(fNew, 0x14, 1, devicecap);
// Update header CRC
fNew.seek(0);
byte[] headerForCRC = new byte[0x15E];
fNew.readFully(headerForCRC);
short crc = CRC16.calculate(headerForCRC, 0, 0x15E);
writeToFile(fNew, 0x15E, 2, (crc & 0xFFFF));
// done
fNew.close();
closeROM();
}
private void copy(RandomAccessFile from, RandomAccessFile to, int bytes) throws IOException {
int sizeof_copybuf = Math.min(256 * 1024, bytes);
byte[] copybuf = new byte[sizeof_copybuf];
while (bytes > 0) {
int size2 = (bytes >= sizeof_copybuf) ? sizeof_copybuf : bytes;
int read = from.read(copybuf, 0, size2);
to.write(copybuf, 0, read);
bytes -= read;
}
}
// get rom code for opened rom
public String getCode() {
return this.romCode;
}
public byte getVersion() {
return this.version;
}
// returns null if file doesn't exist
public byte[] getFile(String filename) throws IOException {
if (files.containsKey(filename)) {
return files.get(filename).getContents();
} else {
return null;
}
}
public byte[] getOverlay(int number) throws IOException {
if (number >= 0 && number < arm9overlays.length) {
return arm9overlays[number].getContents();
} else {
return null;
}
}
public int getOverlayAddress(int number) {
if (number >= 0 && number < arm9overlays.length) {
return arm9overlays[number].ram_address;
} else {
return -1;
}
}
public byte[] getARM9() throws IOException {
if (!arm9_open) {
arm9_open = true;
this.reopenROM();
int arm9_offset = readFromFile(this.baseRom, 0x20, 4);
int arm9_size = readFromFile(this.baseRom, 0x2C, 4);
byte[] arm9 = new byte[arm9_size];
this.baseRom.seek(arm9_offset);
this.baseRom.readFully(arm9);
originalArm9CRC = FileFunctions.getCRC32(arm9);
// footer check
int nitrocode = readFromFile(this.baseRom, 4);
if (nitrocode == 0xDEC00621) {
// found a footer
arm9_footer = new byte[12];
writeToByteArr(arm9_footer, 0, 4, 0xDEC00621);
this.baseRom.readFully(arm9_footer, 4, 8);
arm9_has_footer = true;
} else {
arm9_has_footer = false;
}
// Any extras?
while ((readFromByteArr(arm9, arm9.length - 12, 4) == 0xDEC00621)
|| ((readFromByteArr(arm9, arm9.length - 12, 4) == 0
&& readFromByteArr(arm9, arm9.length - 8, 4) == 0 && readFromByteArr(arm9, arm9.length - 4,
4) == 0))) {
if (!arm9_has_footer) {
arm9_has_footer = true;
arm9_footer = new byte[0];
}
byte[] newfooter = new byte[arm9_footer.length + 12];
System.arraycopy(arm9, arm9.length - 12, newfooter, 0, 12);
System.arraycopy(arm9_footer, 0, newfooter, 12, arm9_footer.length);
arm9_footer = newfooter;
byte[] newarm9 = new byte[arm9.length - 12];
System.arraycopy(arm9, 0, newarm9, 0, arm9.length - 12);
arm9 = newarm9;
}
// Compression?
arm9_compressed = false;
arm9_szoffset = 0;
if (((int) arm9[arm9.length - 5]) >= 0x08 && ((int) arm9[arm9.length - 5]) <= 0x0B) {
int compSize = readFromByteArr(arm9, arm9.length - 8, 3);
if (compSize > (arm9.length * 9 / 10) && compSize < (arm9.length * 11 / 10)) {
arm9_compressed = true;
byte[] compLength = new byte[4];
writeToByteArr(compLength, 0, 4, arm9.length + arm9_ramoffset);
List foundOffsets = RomFunctions.search(arm9, compLength);
if (foundOffsets.size() == 1) {
arm9_szoffset = foundOffsets.get(0);
} else {
throw new RandomizerIOException("Could not read ARM9 size offset. May be a bad ROM.");
}
}
}
if (arm9_compressed) {
arm9 = new BLZCoder(null).BLZ_DecodePub(arm9, "arm9.bin");
}
// Now actually make the copy or w/e
if (writingEnabled) {
File arm9file = new File(tmpFolder + "arm9.bin");
FileOutputStream fos = new FileOutputStream(arm9file);
fos.write(arm9);
fos.close();
arm9file.deleteOnExit();
this.arm9_ramstored = null;
return arm9;
} else {
this.arm9_ramstored = arm9;
byte[] newcopy = new byte[arm9.length];
System.arraycopy(arm9, 0, newcopy, 0, arm9.length);
return newcopy;
}
} else {
if (writingEnabled) {
return FileFunctions.readFileFullyIntoBuffer(tmpFolder + "arm9.bin");
} else {
byte[] newcopy = new byte[this.arm9_ramstored.length];
System.arraycopy(this.arm9_ramstored, 0, newcopy, 0, this.arm9_ramstored.length);
return newcopy;
}
}
}
// returns null if file doesn't exist
public void writeFile(String filename, byte[] data) throws IOException {
if (files.containsKey(filename)) {
files.get(filename).writeOverride(data);
}
}
public void writeOverlay(int number, byte[] data) throws IOException {
if (number >= 0 && number <= arm9overlays.length) {
arm9overlays[number].writeOverride(data);
}
}
public void writeARM9(byte[] arm9) throws IOException {
if (!arm9_open) {
getARM9();
}
arm9_changed = true;
if (writingEnabled) {
FileOutputStream fos = new FileOutputStream(new File(tmpFolder + "arm9.bin"));
fos.write(arm9);
fos.close();
} else {
if (this.arm9_ramstored.length == arm9.length) {
// copy new in
System.arraycopy(arm9, 0, this.arm9_ramstored, 0, arm9.length);
} else {
// make new array
this.arm9_ramstored = null;
this.arm9_ramstored = new byte[arm9.length];
System.arraycopy(arm9, 0, this.arm9_ramstored, 0, arm9.length);
}
}
}
private void firstPassDirectory(int dir, int subTableOffset, int firstFileID, String[] directoryNames,
Map filenames, Map fileDirectories) throws IOException {
// read subtable
baseRom.seek(subTableOffset);
while (true) {
int control = baseRom.read();
if (control == 0x00) {
// done
break;
}
int namelen = control & 0x7F;
byte[] rawname = new byte[namelen];
baseRom.readFully(rawname);
String name = new String(rawname, "US-ASCII");
if ((control & 0x80) > 0x00) {
// sub-directory
int subDirectoryID = readFromFile(baseRom, 2);
directoryNames[subDirectoryID - 0xF000] = name;
} else {
int fileID = firstFileID++;
filenames.put(fileID, name);
fileDirectories.put(fileID, dir);
}
}
}
public void printRomDiagnostics(PrintStream logStream) {
List overlayList = new ArrayList<>();
List fileList = new ArrayList<>();
for (Map.Entry entry : arm9overlaysByFileID.entrySet()) {
if (entry.getValue().originalCRC != 0) {
overlayList.add("overlay9_" + entry.getKey() + ": " + String.format("%08X", entry.getValue().originalCRC));
}
}
for (Map.Entry entry : files.entrySet()) {
if (entry.getValue().originalCRC != 0) {
fileList.add(entry.getKey() + ": " + String.format("%08X", entry.getValue().originalCRC));
}
}
Collections.sort(overlayList);
Collections.sort(fileList);
Path p = Paths.get(this.romFilename);
logStream.println("File name: " + p.getFileName().toString());
logStream.println("arm9: " + String.format("%08X", originalArm9CRC));
for (String overlayLog : overlayList) {
logStream.println(overlayLog);
}
for (String fileLog : fileList) {
logStream.println(fileLog);
}
}
public String getTmpFolder() {
return tmpFolder;
}
public RandomAccessFile getBaseRom() {
return baseRom;
}
public boolean isWritingEnabled() {
return writingEnabled;
}
private int readFromByteArr(byte[] data, int offset, int size) {
int result = 0;
for (int i = 0; i < size; i++) {
result |= (data[i + offset] & 0xFF) << (i * 8);
}
return result;
}
private void writeToByteArr(byte[] data, int offset, int size, int value) {
for (int i = 0; i < size; i++) {
data[offset + i] = (byte) ((value >> (i * 8)) & 0xFF);
}
}
private int readFromFile(RandomAccessFile file, int size) throws IOException {
return readFromFile(file, -1, size);
}
// use -1 offset to read from current position
// useful if you want to read blocks
private int readFromFile(RandomAccessFile file, int offset, int size) throws IOException {
byte[] buf = new byte[size];
if (offset >= 0)
file.seek(offset);
file.readFully(buf);
int result = 0;
for (int i = 0; i < size; i++) {
result |= (buf[i] & 0xFF) << (i * 8);
}
return result;
}
public void writeToFile(RandomAccessFile file, int size, int value) throws IOException {
writeToFile(file, -1, size, value);
}
private void writeToFile(RandomAccessFile file, int offset, int size, int value) throws IOException {
byte[] buf = new byte[size];
for (int i = 0; i < size; i++) {
buf[i] = (byte) ((value >> (i * 8)) & 0xFF);
}
if (offset >= 0)
file.seek(offset);
file.write(buf);
}
}