[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[mhc:01575] Re: Palm



On Fri, 07 Jun 2002 10:07:01 +0900,
	小関 吉則 (KOSEKI Yoshinori) <kose@xxxxxxxxxxxxxxxxxx> said:

> ふと思ったのですが、
> 
> CLIE だと datebook のデータは
> c:/Program Files/SonyPDA/kose/datebook/datebook.dat
> です。これを直接読み書きして HotSync すればいいんじゃないで
> しょうか?

じゃないのかな〜と思って,昨晩
フランス戦を見ながらハックしてみていました.
ColdSync はこの方式のようですね.

とりあえず,構造が簡単なメモ帳から..

pilot-xfer で吸った MemoDB.pdb を
./pdb-dump.rb MemoDB.pdb すると,メモ帳の
内容がだらだら見えると思います.

  c:/Program Files/SonyPDA/kose/

にあるメモ帳のデータではどうでしょうか?
--
nom
#!/usr/local/bin/ruby

require 'kconv'
require 'palm-packer'

$DEBUG = true

## DUMP 時の appinfo と sortinfo の長さが 0 だった場合におかしくなりそう.

################################################################
class PalmAppInfo
################################################################
  include PalmUnpacker
  include PalmPacker

  def initialize(s = nil)
    @category, @id, @modflag, @trailer = [], [], 0, ''
    load(s) if s
  end

  def load(s)
    ptr = 0
    @modflag = UInt16(s[ptr,  2]); ptr += 2

    for i in 0 .. 15
      @category [i] =  Asciiz16(s[ptr, 16]); ptr += 16
      print "category[#{i}] = #{@category[i]}\n" if $DEBUG
    end

    for i in 0 .. 15
      id = UInt8(s[ptr,  1]); ptr += 1
      print "category-id[#{i}] = #{id}\n"
      @id[i] = id
    end

    lastid = UInt8(s[ptr,  1]); ptr += 1
    @trailer = s[ptr .. (s .length - 1)]
    print "appinfo lastid: #{lastid}\n" if $DEBUG
    print "appinfo trailer: #{@trailer}\n" if $DEBUG

    if lastid != category_last_id()
      STDERR .print "Warn: appinfo damaged ?\n"
    end
    debug_report() if $DEBUG
  end

  def dump
    ret = revUInt16(@modflag)
    for i in 0 .. 15
      ret += revAsciiz16(@category[i] || '')
    end
    for i in 0 .. 15
      ret += revUInt8(@id[i])
    end
    ret += revUInt8(category_last_id())
    ret += @trailer
    return ret
  end

  def debug_report
    print "appinfo_category: @modflag = #{@modflag}\n"
    for i in 0 .. 15
      print "appinfo_category: @category[#{i}] = #{@category[i]}\n"
    end
    for i in 0 .. 15
      print "appinfo_category: @id[#{i}] = #{@id[i]}\n"
    end
    print "appinfo_category: last_id: #{category_last_id}\n"
    print "appinfo_category: @trailer: #{@trailer}\n"
  end

  def length
    return dump .length
  end
    
  def add_category(category)
    if !category_exist?(category) and (i = category_new_index())
      @category[i], @id[i], @modflag[i] = category, category_new_id(), 1
      return true
    end
    return nil
  end

  def del_category(category)
    if i = category_name_to_index(category)
      id = @id[i]
      @category[i], @id[i], @modflag[i] = nil, 0, 1
      return true
    end
    return nil
  end

  def ren_category(old, new)
    if i = category_name_to_index(old)
      @category[i], @modflag[i] = new, 1
      return true
    end
    return nil
  end

  def each_category
    @category .each {|c| yield c}
  end

  private
  def category_name_to_index(category)
    return @category .index(category)
  end

  alias category_exits? category_name_to_index

  def category_last_id()
    return (@id .max || 0)
  end

  def category_new_id()
    return category_last_id() + 1
  end

  def category_new_index()
    for i in 0 .. 15
      return i if !@category[i]
    end
    return nil
  end
end

################################################################
class PalmSortInfo
################################################################
  def initialize(s)
    @str = s
  end

  def length
    return @str .length
  end

  def dump
    return @str
  end
end

################################################################
class PalmPdbRecord
################################################################
  def initialize(rid, attr_and_cat, data)
    @rid  = rid
    @attr = attr_and_cat
    @data = data
  end

  def rid  ; return @rid  ;end
  def attr ; return @attr ;end
  def data ; return @data ;end # xxx debug

  def set_id(rid)
    raise "Integer required." if !(id .is_a?(Integer))
    @rid = rid
    return self
  end

  def length
    dump .length
  end

  def deleted?       ; return  (@attr & 0x80 != 0) ;end
  def dirty?         ; return  (@attr & 0x40 != 0) ;end
  def busy?          ; return  (@attr & 0x20 != 0) ;end
  def secret?        ; return  (@attr & 0x10 != 0) ;end
  #  def archived?      ; return  (@attr & 0x08 != 0) ;end
  #  archived flag is for deleted data.
  # so it can be overlap with category data.

  def set_deleted    ; @attr |= 0x80; return self  ;end
  def set_dirty      ; @attr |= 0x40; return self  ;end
  def set_busy       ; @attr |= 0x20; return self  ;end
  def set_secret     ; @attr |= 0x10; return self  ;end
#  def set_archived   ; @attr |= 0x08; return self  ;end

  def reset_deleted  ; @attr &= ~0x80; return self ;end
  def reset_dirty    ; @attr &= ~0x40; return self ;end
  def reset_busy     ; @attr &= ~0x20; return self ;end
  def reset_secret   ; @attr &= ~0x10; return self ;end
#  def reset_archived ; @attr &= ~0x08; return self ;end

  def category       ; return  (@attr & 0x0f)      ;end
  def set_category(c); @attr = (@attr & 0xf0) | (0x0f & c); return self ;end

  def attr_as_string
    attr_str = []
    attr_str << 'Deleted'  if deleted?
    attr_str << 'Dirty'    if dirty?
    attr_str << 'Busy'     if busy?
    attr_str << 'Secret'   if secret?
    # attr_str << 'Archived' if archived?
    return attr_str .join (' ')
  end

  ################
  ## suposed to be be modified in sub class.
  def dump; return @data .to_s; end
end


################################################################
class PalmPdb
################################################################
  include PalmUnpacker
  include PalmPacker

  attr :name,    true
  attr :attr,    true
  attr :version, true
  attr :ctime,   true
  attr :mtime,   true
  attr :btime,   true
  attr :modnum,  true
  attr :type,    true
  attr :creator, true
  attr :idseed,  true

  ### for debug
  attr :records
  attr :gap
  attr :appinfo
  attr :sortinfo
  attr :num_records
  attr :s
  def file_length
    @s .length
  end

  def length
    len = 0
    @records .each{|r|
      len += r .data .length
    }
    return 0x4e + len +
      (@records .length) * 8 +
      (@appinfo || '').length  +
      (@sortinfo || '') .length +
      @gap .length
  end



  def initialize(filename)
    file = File .open(filename)
    @s = file .gets(nil)
    s = @s

    @record_class   = PalmPdbRecord
    @name           = Asciiz32 (s[0x00, 32])
    @attr           = UInt16   (s[0x20,  2])
    @version        = UInt16   (s[0x22,  2])
    @ctime          = PalmDate (s[0x24,  4])
    @mtime          = PalmDate (s[0x28,  4])
    @btime          = PalmDate (s[0x2c,  4])
    @modnum         = UInt32   (s[0x30,  4])
    appinfo_id      = LocalID  (s[0x34,  4])
    sortinfo_id     = LocalID  (s[0x38,  4])
    @type           = Ascii4   (s[0x3c,  4])
    @creator        = Ascii4   (s[0x40,  4])
    @idseed         = UInt32   (s[0x44,  4])
    next_reclist_id = LocalID  (s[0x48,  4])
    num_records     = UInt16   (s[0x4c,  2])

    @num_records = num_records # for debug

    if next_reclist_id != 0
      raise "Unsupported format."
    end

    ################
    ## make array of records.
    @records = []
    for i in 1 .. num_records
      ptr       = 0x4e + (i - 1) * 8

      print format("rexlist  offset %d  %x -> %x\n", i, ptr, ptr + 8)

      rec_b = LocalID(s[ptr + 0, 4]) # 4e 4f 50 51
      attr  = UInt8  (s[ptr + 4, 1]) # 52
      rid   = UInt24 (s[ptr + 5, 3]) # 53
      rec_e = if i == num_records then
		    s .length - 1
		  else
		    LocalID(s[ptr + 8, 4]) - 1
		  end
     print format("record offset %x -> %x\n", rec_b, rec_e)

      @records <<  @record_class .new(rid, attr, s[rec_b .. rec_e])
      # print "#{rec_b} -> #{rec_e} (#{rec_e - rec_b + 1})\n"
      # @records <<  s[rec_b .. rec_e]
    end

    ################
    ## set each start and end point.
    app_b, sor_b, rec_b, length = nil, nil, nil, s .length

    gap_b = if length > 0x4e    then 0x4e + num_records * 8 end
    app_b = if appinfo_id  != 0 then appinfo_id             end
    sor_b = if sortinfo_id != 0 then sortinfo_id            end
    rec_b = if num_records > 0  then LocalID(s[0x4e, 4])    end

    gap_e   = (app_b || sor_b || rec_b || out) - 1
    app_e   = (         sor_b || rec_b || out) - 1
    sor_e   = (                  rec_b || out) - 1

    @gap      =                   s[gap_b .. gap_e]  if gap_b && gap_b < gap_e
    @appinfo  = PalmAppInfo  .new(s[app_b .. app_e]) if app_b
    @sortinfo = PalmSortInfo .new(s[sor_b .. sor_e]) if sor_b
  end

  ################################################################
  ## functions for dump
  ##  whole DB is made up by
  ##       header + rec_list + gap + appinfo + sortinfo + records
  def dump_header
    print "xxxxxxxxxxxxxxxxxxxxxxxxx\n"
    p @ctime
    p @mtime
    p @btime
    print "xxxxxxxxxxxxxxxxxxxxxxxxx\n"

    if @sortinfo and @sortinfo .length > 0
      sortinfooffset = (appinfooffset + @appinfo .length)
    else
      sortinfooffset = 0
    end

    return revAsciiz32(@name) +
      revUInt16(@attr) +
      revUInt16(@version) +
      revPalmDate(@ctime) +
      revPalmDate(@mtime) +
      revPalmDate(@btime) +
      revUInt32(@modnum) +
      revLocalID(appinfooffset =
		 (0x4e + (@records .length * 8) + @gap .length)) +
      revLocalID(sortinfooffset) +
      revAscii4(@type) +
      revAscii4(@creator) +
      revUInt32(@idseed) +
      revUInt32(nextRecordListID = 0) +
      revUInt16(@records .length)
  end

  def dump_reclist
    ret = ''

    offset = 0x4e + (@records .length * 8) +
      @gap .length +
      @appinfo .length +
      (@sortinfo || '') .length
    

    @records .each{|r|
      print format("offset of reclist = %x\n", offset) if $DEBUG
      ret += revLocalID(offset) + revUInt8(r .attr) + revUInt24(r .rid)
      offset += r .length
    }
    return ret
  end

  def dump_gap
    return @gap
  end

  def dump_appinfo
    return @appinfo .dump
  end

  def dump_sortinfo
    if @sortinfo 
      return @sortinfo .dump
    else
      return ''
    end
  end

  def dump_records
    ret = ''
    @records .each{|r| ret += r .dump}
    return ret
  end

  def dump
    return dump_header + dump_reclist + dump_gap + dump_appinfo +
      dump_sortinfo + dump_records
  end

  ################################################################
  ## manupulate records in DB.

  def record_by_index(i)
    return @records[i]
  end

  def record_by_id(id)
    return @records[id_to_index[id]]
  end

  def each_record
    i = 0
    while (rec = record_by_index(i)) != nil
      yield rec
      i += 1
    end
  end

  def delete_by_id(id)
    @records .delete_at(id_to_index(id))
    return self
  end

  def delete_all
    @records .clear
    return self
  end

  ## Delete all records which are marked as archived or deleted.
  def cleanup_record
    @records .each{|r|
      if r .archived? or r .deleted?
	@records .delete_by_id(r .id)
      end
    }
    return self
  end

  ## Reset all dirty flags. set the last sync time to now.
  def reset_sync_flags
    ## xxx
    return self
  end

  private
  def id_to_index(id)
    @records .each_index{|i| return i if @records[i] .id == id}
    return nil
  end
end

pdb = PalmPdb.new(ARGV[0])

print Kconv::toeuc("Name: ``#{pdb .name}''\n")
print "Version: #{pdb .version}\n"
print "Ctime: #{pdb .ctime}\n"
print "Mtime: #{pdb .mtime}\n"
print "Btime: #{pdb .btime}\n"
print "Modnum: #{pdb .modnum}\n"
print "Type: #{pdb .type}\n"
print "Creator: #{pdb .creator}\n"
print "IDseed: #{pdb .idseed}\n"
print "NumRecords: #{pdb .num_records}(info) / #{pdb .records .length}(net)\n"

print "RecordsLen: #{pdb .records .to_s .length}\n"
print "AppinfoLen: #{pdb .appinfo .to_s .length}\n"
print "SortinfoLen: #{pdb .sortinfo .to_s .length}\n"
print "GapLength: #{pdb .gap .length}\n"
print "FileLength: #{pdb .file_length}\n"
print "DBLength: #{pdb .length}\n"

pdb .records .each_with_index{|r,i|
  print "****************************************************************\n"
  print "Record #{i}:  length: #{r .length} ID: #{r .rid}\n"
  print "Attr: #{r .attr_as_string}\n"
  print "Category: #{r .category}\n"
  print r .data .gsub("\0", "\n"), "\n"
  print "****************************************************************\n\n"
}

x = pdb .dump
file = File .open(ARGV[0])
s = file .gets(nil)

print "S = #{s .length}\n"
print "X = #{x .length}\n"
copy_file = File .open(ARGV[0] + '.bak', "w")
copy_file .print x
copy_file .close
require 'kconv'

################################################################
## modules
module PalmPacker
  def revUIntN(i, n)
    bytes, ret = n / 8, []

    if i .kind_of?(Integer) and 0 <= i and i <= 256 ** bytes - 1
      for c in 0 .. bytes - 1
	ret[bytes - 1 - c] = (i / (256 ** c)) % 256
      end
      return ret .pack('C' * bytes)
    end
    raise "Wrong value #{i .inspect} for revUInt#{n}."
  end

  def revUInt32(i);  return revUIntN(i, 32) ; end
  def revUInt24(i);  return revUIntN(i, 24) ; end
  def revUInt16(i);  return revUIntN(i, 16) ; end
  def revUInt8(i) ;  return revUIntN(i,  8) ; end

  def revPalmDate(d)
    if d .kind_of?(Time)
      return revUInt32(d .to_i + 2082844800)
    else d .kind_of?(Integer)
      return revUInt32(d)
    end
    raise format("Wrong value #{d .inspect} for PalmDate.")
  end

  def revAscii4(s)
    if s .kind_of?(String) and s .length == 4
      return s
    end
    raise format("Wrong value '#{s}' for Ascii4.")
  end

  def revAsciiz16(s)
    if s .kind_of?(String) and s .length <= 15
      return (s + "\0" * 16)[0, 16]
    end
    raise format("Wrong value '#{s}' for Asciiz16.")
  end

  def revAsciiz32(s)
    if s .kind_of?(String) and s .length <= 31
      return (s + "\0" * 32)[0, 32]
    end
    raise format("Wrong value '#{s}' for Asciiz32.")
  end

  alias revLocalID revUInt32
end

module PalmUnpacker
  def UIntN(s, n)
    bytes, ret = n / 8, 0
    s .unpack('C' * bytes) .each_with_index{|c, i|
      ret += c * (256 ** (bytes - 1 - i))
    }
    return ret
  end

  def UInt32(s);  return UIntN(s, 32) ; end
  def UInt24(s);  return UIntN(s, 24) ; end
  def UInt16(s);  return UIntN(s, 16) ; end
  def UInt8(s) ;  return UIntN(s,  8) ; end

  def PalmDate(s)
    sec_from_epoc = UInt32(s) - 2082844800
    if sec_from_epoc >= 0
      return Time .at(sec_from_epoc)
    else
      return UInt32(s)
    end
  end

  def Ascii4(s)
    return s[0,4]
  end

  def AsciizN(s, n)
    return s[0, n]   .sub(/\0+$/, '')
  end

  def Asciiz32(s)
    AsciizN(s, 32)
  end

  def Asciiz16(s)
    AsciizN(s, 16)
  end

  alias LocalID UInt32
end