View previous topic :: View next topic |
Author |
Message |
garlicbread Apprentice
Joined: 06 Mar 2004 Posts: 182
|
Posted: Mon Apr 10, 2006 10:08 pm Post subject: convinitrd - easy access to initrd / initramfs images |
|
|
Convinitrd
This might be interesting to some
a script that will allow you to easily exctract an initrd / initramfs image to a directory and then back to an image
this way you can make custom changes to initramfs images generated by genkernel
Warning always have a backup image handy to boot from via a second entry in grub (or whatever bootloader your using), otherwise you may end up with an unbootable system
I initially wrote this back when genkernel lacked dmraid support to add in any libs or static executables I might need into the initramfs image
but since genkernel now does a pretty good job of creating these images, the only time I use this is when I have a problem with something, or if I want to add in another splash theme into the image
I've borrowed some bits from genkernel and a couple of other places
the stuff relating to initrd's is probably obsolete since now that everyone is using initramfs images for kernel 2.6
it's probably not anywhere near perfect but it makes editing or changing initrd / initramfs files a whole lot easier
it requires cpio for initramfs, and loopback support for initrd's (for mounting)
some examples of use:
Code: |
# extract an existing initrd image into a directory called test
convinitrd -d test.initrd.gz
# extract an existing initramfs image into a directory called test
convinitrd -d test.cpio.gz
# convert a directory called "test" & files in the directory into an initrd image called "test.initrd.gz"
convinitrd -i test/
# convert a directory called "test" & files in the directory into an initramfs image called "test.cpio.gz"
convinitrd -r test/
# show all options
convinitrd --help |
if you want to mess around with the images from genkernel, something to note is that images from genkernel are compressed by default (but have no .gz extension
also they consist of a series of cpio images tar'd together instead of a single cpio image, so we need the -g option to perform the initial extraction
any compression into initramfs done from the script is done via a single cpio compressed image
example
Code: |
# create an initial image
cd /boot/
genkernel --dmraid --gensplash=livecd-2005.1 initrd
# the conversion script won't recognise it as compressed without a .gz extension
mv initramfs-genkernel-x86_64-2.6.16-mygen-r1 initramfs-genkernel-x86_64-2.6.16-mygen-r1.cpio.gz
# we need to add -g for the initial extraction for images from genkernel, ignore the error about "premature end of archive"
/usr/local/bin/convinitrd -d -g initramfs-genkernel-x86_64-2.6.16-mygen-r1.cpio.gz
# we should now have a directory called initramfs-genkernel-x86_64-2.6.16-mygen-r1/ inside boot
# make any changes needed
# re-compress
/usr/local/bin/convinitrd -r initramfs-genkernel-x86_64-2.6.16-mygen-r1
# end result - file /boot/initramfs-genkernel-x86_64-2.6.16-mygen-r1.cpio.gz
|
There are some limitations to be aware of
the script will only recognise if the initrd is compressed if it has a .gz extension
(sometimes initrd's that have been created by other means like genkernel will ommit the .gz extension in the name, even though they're compressed)
the script always adds .initrd or .cpio (for initramfs) to the end of the name when converting a dir to an image
and it will always add .gz for compressed images (so a directory called test.1.2.3 would become test.1.2.3.initrd.gz or test.1.2.3.cpio.gz)
it's also possible to convert a initrd image file format into a initramfs file format with
Code: | convinitrd -rm test.initrd.gz |
the m option will attempt to rename the linuxrc script to init in the archive during the conversion
however this is often not enough for the image to boot properly as there are some other differences as well
in this case it's better to simply re-create the image from scratch in this situation using genkernel that way you don't have to worry about using run-init from klibc instead of pivot_root
convinitrd script
Code: |
#!/bin/bash
#
# convinitrd - convert between different types of boot images initrd / iniramfs / directory type file formats
# This program is distributed under the terms of GPL version 2.
# written by Richard Westwell <garlicbread@ntlworld.com>
#
# some code borrowed from the mkinitrd / qpkg / genkernel scripts
ID='$Id: convinitrd,v 1.2 2005/02/10 00:00:00 genone Exp $'
VERSION=0.`echo ${ID} | cut -d\ -f3`
TMPDIR="/tmp/convinitrd-${$}/"
rm -rf ${TMPDIR}
mkdir -p ${TMPDIR}
PROG=`basename "${0}"`
# ${funcreturn} - global variable to be used to return values
funcreturn=""
verb="0"
# Default compression
# 0 = none, 1 = gzip, 2 = bzip2
compress=1
# Type of fs to use for initrd images
# and the command to use for creating the fs
fstype="ext2"
mkfscommand="mke2fs"
# number of Kb blocks to leave spare when creating the initrd fs
fs_spare="100"
# number of inodes to leave spare when creating the initrd fs
inode_spare="100"
#####################
#Function Definitions
#####################
# general cleanup after exit
# minimal version
function gen_die {
echo "${1}" >&2
# rm -rf "${TMPDIR}"
exit 1
}
DEBUGFILE="/dev/null"
KV="`uname -r`"
#################################################
# Functions taken / modified from genkernel 3.1.6
#################################################
# Calculate the size of the initrd needed
# by looking inside ${TEMP}/initrd-temp/
calc_initrd_size() {
local TEST
cd ${TEMP}/initrd-temp/
TEST=`du -skl 2> /dev/null`
echo $TEST | cut "-d " -f1
}
# create an empty inird image and mount
# parameter ${1} = the size of the initrd in Kb
# parameter ${2} = number of inodes to use
create_initrd_loop() {
[ "$#" -ne '2' ] && gen_die 'create_initrd_loop(): Not enough arguments!'
local inodes="${2}"
mkdir -p ${TEMP}/initrd-mount ||
gen_die 'Could not create loopback mount directory!'
dd if=/dev/zero of=${TEMP}/initrd-${KV} bs=1k count=${1} >> "${DEBUGFILE}" 2>&1 ||
gen_die "Could not zero initrd-${KV}"
mke2fs -F -N${inodes} -q "${TEMP}/initrd-${KV}" >> "${DEBUGFILE}" 2>&1 ||
gen_die "Could not format initrd-${KV}!"
mount -t ext2 -o loop "${TEMP}/initrd-${KV}" "${TEMP}/initrd-mount" >> "${DEBUGFILE}" 2>&1 ||
gen_die 'Could not mount the initrd filesystem!'
rm -Rf "${TEMP}/initrd-mount/lost+found"
}
# umount the initrd
create_initrd_unmount_loop()
{
cd ${TEMP}
umount "${TEMP}/initrd-mount" ||
gen_die 'Could not unmount initrd system!'
}
#######################################
# End of Functions from genkernel
#######################################
# mount an initrd for reading
create_initrd_mount_loop()
{
mkdir -p "${TEMP}/initrd-mount" ||
gen_die 'Could not create loopback mount directory!'
mount -t ext2 -o loop "${TEMP}/initrd-${KV}" "${TEMP}/initrd-mount" >> "${DEBUGFILE}" 2>&1 || {
return -1
}
return 0
}
# calculate the number of files / directories from the current dir down
# this can be useful for getting an inode count
calc_file_count() {
local funcname="calc_file_count:"
local count="0"
for a in `du -a`;do
test -e "$a" && count=$(($count+1))
done
local count2=$(($count-1))
echo $count2 | cut "-d " -f1
}
# Convert a directory to a initrd image
# parameter ${1} = path to the directory
# return value equals the initrd image created
dir_to_initrd() {
local funcname="dir_to_initrd:"
[ "$#" -ne '1' ] && gen_die "${funcname} at least 1 parameter is needed"
TEMP="${TMPDIR}/dir_to_initrd"
local curdir=`pwd`
local base_name=`basename "${1}"`
local rdimage="${TEMP}/initrd-${KV}"
mkdir -p "${TEMP}" || gen_die "${funcname} Error creating temporaries."
cp -a "${1}" "${TEMP}/initrd-temp" || gen_die "${funcname} Error copying source dir"
cd "${TEMP}/initrd-temp"
[ "${moverc}" ] && mv -i init linuxrc
local INITRD_CALC_SIZE=`calc_initrd_size`
local INITRD_SIZE=`expr ${INITRD_CALC_SIZE} + ${fs_spare}`
[ "${initrdsizefixed}" ] && INITRD_SIZE="${fixedsize}"
[ "${verb}" -gt "0" ] && echo "${funcname} Using a initrd size of ${INITRD_SIZE} Kb"
cd "${TEMP}/initrd-temp"
local cfc=`calc_file_count`
local inodecount=$(($cfc + $inode_spare))
[ "${verb}" -gt "0" ] && echo "${funcname} number of inodes used ${inodecount}"
create_initrd_loop ${INITRD_SIZE} ${inodecount}
# Move everything from ${TEMP}/initrd-temp to ${TEMP}/initrd-mount
mv * "${TEMP}/initrd-mount" ||
gen_die "${funcname} There was a problem moving the data across into the initrd"
create_initrd_unmount_loop
mv "${rdimage}" "${TEMP}/${base_name}.initrd"
rdimage="${TEMP}/${base_name}.initrd"
cd ${curdir}
if [ "${compress}" = "1" ]; then
gzip -9 "${rdimage}" || gen_die "${funcname} There was a problem compressing the initrd"
rdimage="${rdimage}.gz"
elif [ "${compress}" = "2" ]; then
bzip2 -9 "${rdimage}" || gen_die "${funcname} There was a problem compressing the initrd"
rdimage="${rdimage}.bz2"
fi
funcreturn="${rdimage}"
}
# Convert a initrd image to a directory
# parameter ${1} = path to the initrd image
# return value equals the path to the created directory
initrd_to_dir() {
local funcname="initrd_to_dir:"
[ "$#" -ne '1' ] && gen_die "${funcname} at least 1 parameter is needed"
TEMP="${TMPDIR}/initrd_to_dir"
local base_name=`basename "${1}"`
local uc_name=`echo "${base_name}" | sed "s/.initrd$//"`
mkdir -p "${TEMP}" ||
gen_die "${funcname} Error creating temporaries."
mkdir -p "${TEMP}/extracted/${uc_name}" ||
gen_die "${funcname} Error creating temporaries."
cp -a "${1}" "${TEMP}/initrd-${KV}" ||
gen_die "${funcname} Error copying the source initrd"
create_initrd_mount_loop || gen_die "${funcname} unable to mount initrd"
[ "${verb}" -gt "0" ] && echo "${funcname} copying data"
cp -a ${TEMP}/initrd-mount/* "${TEMP}/extracted/${uc_name}/" || {
create_initrd_unmount_loop
gen_die "${funcname} Problem with extracting the data"
}
create_initrd_unmount_loop
funcreturn="${TEMP}/extracted/${uc_name}"
}
# Convert a directory to a initramfs image
# parameter ${1} = path to the directory
# return value equals the path to the created cpio file
dir_to_initramfs() {
local funcname="dir_to_initramfs:"
[ "$#" -ne '1' ] && gen_die "${funcname} at least 1 parameter is needed"
TEMP="${TMPDIR}/dir_to_initramfs"
local curdir=`pwd`
local base_name=`basename "${1}"`
local dir_name=`dirname "${1}"`
mkdir -p ${TEMP}
cd "${dir_name}/${base_name}" 2>/dev/null || gen_die "${funcname} unable to enter the directory ${dir_name}/${base_name}"
[ "${moverc}" ] && mv -i linuxrc init
[ "${verb}" -gt "0" ] && echo "${funcname} creating cpio (initramfs) image from ${dir_name}/${base_name}"
find . | cpio -o -H newc >"${TEMP}/${base_name}.cpio"
cd ${curdir}
if [ "${compress}" = "0" ]; then
base_name="${base_name}.cpio"
elif [ "${compress}" = "1" ]; then
gzip -9 "${TEMP}/${base_name}.cpio"
base_name="${base_name}.cpio.gz"
elif [ "${compress}" = "2" ]; then
bzip2 -9 "${TEMP}/${base_name}.cpio"
base_name="${base_name}.cpio.bz2"
fi
funcreturn="${TEMP}/${base_name}"
}
# Convert a initramfs image to a directory
# parameter ${1} = path to the initramfs file
# paramter ${2} if this equals "mp" attempt a multi-part extraction
# return value equals the path to the created directory
initramfs_to_dir() {
local funcname="initramfs_to_dir:"
[ "$#" -lt '1' ] && gen_die "${funcname} at least 1 parameter is needed"
TEMP="${TMPDIR}/initramfs_to_dir"
local base_name=`basename "${1}"`
local dir_name=`dirname "${1}"`
local targetdir=`echo "${base_name}" | sed "s/.cpio$//"`
mkdir -p "${TEMP}/${targetdir}"
[ "${verb}" -gt "0" ] && echo "${funcname} extracting cpio (initramfs) image from ${dir_name}/${base_name}"
if [ "${2}" == "mp" ]; then
# Convert a initramfs that's been generated by catt'ing together several cipo files (e.g. genkernel)
(cat "${dir_name}/${base_name}") | (exitval=0; while [ $exitval -eq 0 ]; do cd "${TEMP}/${targetdir}"; cpio -i || exitval=1;done)
else
# Convert a single initramfs file
(cat "${dir_name}/${base_name}") | (cd "${TEMP}/${targetdir}"; cpio -i)
fi
funcreturn="${TEMP}/${targetdir}"
}
# auto decompress the source file
# if there is no compression (e.g. a directory) leave the source where it is
# if there is compression decompress into a temporary dir
# parameter ${1} = path to the source file
# return value equals the path to the extracted file
auto_decompress() {
local funcname="auto_decompress:"
[ "$#" -ne '1' ] && gen_die "${funcname} at least 1 parameter is needed"
TEMP="${TMPDIR}/auto_decompress"
local base_name=`basename "${1}"`
local dir_name=`dirname "${1}"`
mkdir -p ${TEMP}
local tmp_file="${TEMP}/${base_name}"
[ "$verb" -gt "0" ] && echo "${funcname} decompressing ${dir_name}/${base_name}"
local compressext=`echo ${base_name} | sed "s/.*\.//"`
local uc_name="${base_name}"
if [ -d "${1}" ]; then
funcreturn="${1}"
elif [ "${compressext}" = "gz" ]; then
cp "${dir_name}/${base_name}" "${tmp_file}" || gen_die "${funcname} unable to access ${dir_name}/${base_name}"
gzip -d "${tmp_file}"
uc_name=`echo ${base_name} | sed "s/.gz$//"`
#mv -i ${TEMP}/${uc_name} ${dir_name}/
funcreturn="${TEMP}/${uc_name}"
elif [ "${compressext}" = "bz2" ]; then
cp "${dir_name}/${base_name}" "${tmp_file}" || gen_die "${funcname} unable to access ${dir_name}/${base_name}"
bzip2 -d "${tmp_file}"
uc_name=`echo ${base_name} | sed "s/.bz2$//"`
#mv -i ${TEMP}/${uc_name} ${dir_name}/
funcreturn="${TEMP}/${uc_name}"
else
funcreturn="${1}"
fi
}
usage() {
echo -e "${CY}${PROG} v. ${VERSION}${NO}
${CY}${PROG}${NO} can be used to quickly convert between different forms of initrd image,
allow easy editing of existing images, or allow images to be created from scratch
(by changing a directory into an image and then back again quickly)
${BR}Usage:
${T}${CY}${PROG}${NO} [${BR}output selection${NO}] [${BR}options${NO}] [${YL}srcfile | srcdir${NO}]
${T}${CY}${PROG}${NO} ${BL}--help${NO}
${BR}Output Selection:
${BL}-d, --output=directory${NO}${T}Convert the source into a directory containing files from the archive
${BL}-i, --output=initrd${NO}${T}Convert the source into a initrd (${fstype} filesystem) image
${BL}-r, --output=initramfs${NO}${T}Convert the source into a initramfs (cpio newc format) image
${BL}--initrdsize <size in Kb>${NO}${T}Specify the size to use for initrd type archives
${BR}Input Selection:
${BL}--input=directory${NO}${T}Manually select the input as a directory type
${BL}--input=initrd${NO}${T}${T}Manually select the input as a initrd image type (${fstype} filesystem)
${BL}--input=initramfs${NO}${T}Manually select the input as a initramfs image type (cpio newc format)
${BL}--input=genkernel_cpio, -g${NO}${T}Manually select the input as a joined together cpio image (genkernel)
${BR}Process Modifiers:
${BL}-k, --keep${NO}${T}${T}Keep the source file
${BL}-m, --moverc${NO}${T}${T}move the linuxrc script to init script (for initrd to initramfs conversion)
${NO}${T}${T}${T}or move the init script ro linuxrc script (for initramfs to initrd conversion)
${BR}Compression Selection:
${BL}-c0, --compress=none${NO}${T}sets the level of compression to none for outputed files
${BL}-c1, --compress=gzip${NO}${T}sets the level of compression to gzip for outputed files
${BL}-c2, --compress=bzip2${NO}${T}sets the level of compression to bzip2 for outputed files
${BR}Operation Modifiers:
${BL}-nc, --no-color${NO}${T}don't use colors
${BL}-v, --verbose${NO}${T}${T}Be more verbose
${YL}Notes${NO}:
${YL}*${NO} The Default compression is -c${compress}
${YL}*${NO} the script will attempt to auto detect the input type unless --input= is specified
${YL}*${NO} the -m or --moverc option can move the linuxrc script within the archive to a script called init
for initramfs, or visa-versa but this is often not enough to just convert an initrd image to an
initramfs image (run-init has to be used within initramfs instead of pivot_root at the very least)
${YL}Examples${NO}:
${PROG} -d bootrd.initrd.gz
Create a directory called bootrd, extract the files from the source image into this directory
and remove the source file
${PROG} -c0 -r -k bootrd.initrd.gz
Create a cpio archive file called bootrd.cpio containing the files from bootrd.initrd.gz
with no compression and to keep the original source file bootrd.initrd.gz
"
gen_die
}
###########
#Parse Args
###########
input_auto_detect=1
input_type=0
out_dir=0
out_initrd=0
out_initramfs=0
funcname="convinitrd:"
params=${#}
while [ ${#} -gt 0 ]
do
a=${1}
shift
case "${a}" in
-h|--help)
usage=y
break
;;
-v|--verbose)
let $((verb++))
;;
-d|--output=directory)
out_dir=1
;;
-i|--output=initrd)
out_initrd=1
;;
-r|--output=initramfs)
out_initramfs=1
;;
--input=directory)
input_auto_detect=0
input_type=1
;;
--input=initrd)
input_auto_detect=0
input_type=2
;;
--input=initramfs)
input_auto_detect=0
input_type=3
;;
-g|--input=genkernel_cpio)
input_auto_detect=0
input_type=4
;;
-c0|--compress=none)
compress=0
;;
-c1|--compress=gzip)
compress=1
;;
-c2|--compress=bzip2)
compress=2
;;
-k|--keep)
keep_original=y
;;
-nc|--no-color|--nocolor|--no-colors|--nocolors)
nocolor=y
;;
-m|--moverc)
moverc=y
;;
--initrdsize)
initrdsizefixed=y
fixedsize=$1
shift
;;
-*)
echo -e ${CY}${PROG}${NO}:${YL} Invalid option ${RD}$a 1>&2
usage=y
break
;;
*)
if [ -n "${arg}" ]; then
echo -e ${CY}${PROG}: ${YL}Only one argument supported
usage=y
break
fi
arg=$a
;;
esac
done
T="\t"
#Set up colors
if [ ! "${nocolor}" ]; then
NO="\x1b[0;0m"
BR="\x1b[0;01m"
CY="\x1b[36;01m"
RD="\x1b[31;01m"
GR="\x1b[32;01m"
YL="\x1b[33;01m"
BL="\x1b[34;01m"
fi
[ "${usage}" ] && usage
# Test for valid option combinations
tempinput=$[${out_dir}+${out_initrd}+${out_initramfs}]
[ "${tempinput}" -gt "1" ] && gen_die "${funcname} specify only one output mode -d -i or -r"
if [ "${tempinput}" -eq "0" ]; then
echo "${funcname} specify at least one output mode -d -i or -r" >&2
echo ""
usage
gen_die
fi
# make sure this is the full absolute path
bas_nm=`basename "${arg}"`
dir_nm=`dirname "${arg}"`
[ "${dir_nm}" = "." ] && dir_nm=`pwd`
original_file="${dir_nm}/${bas_nm}"
# Determine the input type
[ "${verb}" -gt "0" ] && echo "${funcname} Checking the input type"
if [ "${input_auto_detect}" = "1" ]; then
if [ -d "${arg}" ]; then
input_type=1
else
# attempt to decompress the source file
auto_decompress "${arg}"
arg="${funcreturn}"
# Test for a initramfs image
cat "${arg}" | cpio -t -H newc 1>/dev/null 2>&1 && input_type=3
fi
# Test for an initrd image
if [ "${input_type}" = "0" ]; then
TEMP="${TMPDIR}/"
cp -a "${arg}" "${TEMP}/initrd-${KV}"
create_initrd_mount_loop && {
create_initrd_unmount_loop
input_type=2
}
fi
else
if [ ! -d "${arg}" ]; then
# attempt to decompress the source file
auto_decompress "${arg}"
arg="${funcreturn}"
fi
fi
[ "${input_type}" = "0" ] && gen_die "unrecognised input type"
# ${input_type} should now contain the type of the input parameter
# 1 = directory, 2 = initrd, 3 = initramfs
if [ "${verb}" -gt "0" ]; then
[ "${input_type}" = "1" ] && echo "${funcname} input is a directory"
[ "${input_type}" = "2" ] && echo "${funcname} input is a initrd image"
[ "${input_type}" = "3" ] && echo "${funcname} input is a initramfs image"
[ "${input_type}" = "4" ] && echo "${funcname} input is a multi-part cpio (genkernel) image"
fi
[ "${input_type}" = "1" ] && [ "${out_dir}" = "1" ] && gen_die "${funcname} input and output are the same format"
[ "${input_type}" = "2" ] && [ "${out_initrd}" = "1" ] && gen_die "${funcname} input and output are the same format"
[ "${input_type}" = "3" ] && [ "${out_initramfs}" = "1" ] && gen_die "${funcname} input and output are the same format"
if [ "${input_type}" = "1" ]; then
[ "${out_initrd}" = "1" ] && dir_to_initrd "${arg}"
[ "${out_initramfs}" = "1" ] && dir_to_initramfs "${arg}"
elif [ "${input_type}" = "2" ]; then
[ "${out_dir}" = "1" ] && initrd_to_dir "${arg}"
[ "${out_initramfs}" = "1" ] && initrd_to_dir "${arg}" && dir_to_initramfs "${funcreturn}"
elif [ "${input_type}" = "3" ]; then
[ "${out_dir}" = "1" ] && initramfs_to_dir "${arg}"
[ "${out_initrd}" = "1" ] && initramfs_to_dir "${arg}" && dir_to_initrd "${funcreturn}"
elif [ "${input_type}" = "4" ]; then
[ "${out_dir}" = "1" ] && initramfs_to_dir "${arg}" "mp"
[ "${out_initrd}" = "1" ] && initramfs_to_dir "${arg}" "mp" && dir_to_initrd "${funcreturn}"
[ "${out_initramfs}" = "1" ] && initramfs_to_dir "${arg}" "mp" && dir_to_initramfs "${funcreturn}"
fi
# If the original file and destination are the same, rename the original to <filename>.initrd or .cpio
# to avoid name conflicts
if [ "`basename "${funcreturn}"`" = "`basename "${original_file}"`" ]; then
if [ "${input_type}" = "2" ]; then
echo "${funcname} moving original file "`basename "${original_file}"`" to "`basename "${original_file}"`".initrd"
mv -i "${original_file}" "${original_file}.initrd"
original_file="${original_file}.initrd"
elif [ "${input_type}" = "3" ] || [ "${input_type}" = "4" ]; then
echo "${funcname} moving original file "`basename "${original_file}"`" to "`basename "${original_file}"`".cpio"
mv -i "${original_file}" "${original_file}.cpio"
original_file="${original_file}.cpio"
fi
fi
mv -i "${funcreturn}" "`dirname "${original_file}"`""/" ||
gen_die "${funcname} unable to create the destination `dirname "${original_file}"`/`basename "${funcreturn}"`"
[ ! "${keep_original}" ] && rm -Rf "${original_file}"
rm -rf "${TMPDIR}"
|
|
|
Back to top |
|
|
coolsnowmen Veteran
Joined: 30 Jun 2004 Posts: 1479 Location: No.VA
|
Posted: Thu Apr 13, 2006 10:10 pm Post subject: just thinking |
|
|
cool script!
I noticed that you require your compessed files to have a .gz extension.
If you wanted to fix that, you could use the /usr/bin/file command
maybe something like this
local compresstype=`/usr/bin/file -b ${base_name} `
would return :
gzip compressed data, [and then more]
using:
/usr/bin/file initrd -ib
would then return:
application/x-gzip
I only say it incase you hadnt thought about it / realized how east that string compare would be, not to make more work for you |
|
Back to top |
|
|
mikegpitt Advocate
Joined: 22 May 2004 Posts: 3224
|
Posted: Tue Jan 15, 2008 7:55 pm Post subject: |
|
|
I was looking for this thread for a while today, and finally found it!
Nice script... used it before and want to use it again, and I'm posting so I don't loose it a third time |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|