Skip to content
This repository was archived by the owner on Dec 27, 2024. It is now read-only.

Commit a713eb2

Browse files
committed
Initial commit
0 parents  commit a713eb2

6 files changed

+334
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.vs
2+
/bin
3+
/obj

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT license
2+
3+
Copyright 2017 Vyacheslav Napadovsky <[email protected]>.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

Program.cs

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// MIT license
2+
// Copyright (C) Vyacheslav Napadovsky, 2017
3+
4+
using System;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Xml.Linq;
9+
10+
namespace wp2droid4sms {
11+
12+
public class Converter {
13+
private static long WpTs2Droid(long wpTs) {
14+
return wpTs / (10 * 1000) - 11644473600000;
15+
}
16+
17+
private static long DroidTs2Wp(long droidTs) {
18+
return (droidTs + 11644473600000) * (10 * 1000);
19+
}
20+
21+
private static long ToDroidTime(DateTime dt) {
22+
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
23+
return Convert.ToInt64((dt - epoch).TotalMilliseconds);
24+
}
25+
26+
private static DateTime FromDroidTime(long droidTs) {
27+
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
28+
return epoch.AddMilliseconds(droidTs);
29+
}
30+
31+
private static XElement CreateDroidSMS(string address, long droidTs, bool incoming, bool read, string body) {
32+
return new XElement("sms",
33+
new XAttribute("address", address),
34+
new XAttribute("body", body),
35+
new XAttribute("contact_name", "(Unknown)"),
36+
new XAttribute("date", droidTs.ToString()),
37+
new XAttribute("date_sent", droidTs.ToString()),
38+
new XAttribute("locked", "0"),
39+
new XAttribute("protocol", "0"),
40+
new XAttribute("read", read ? "1" : "0"),
41+
new XAttribute("readable_date", FromDroidTime(droidTs).ToString("G")),
42+
new XAttribute("sc_toa", "null"),
43+
new XAttribute("service_center", "null"),
44+
new XAttribute("status", incoming ? "-1" : "0"),
45+
new XAttribute("subject", "null"),
46+
new XAttribute("toa", "null"),
47+
new XAttribute("type", incoming ? "1" : "2")
48+
);
49+
}
50+
51+
public static void ConvertToAndroid(XDocument wpFormatSMS, XDocument wpFormatMMS, out XDocument droidFormat) {
52+
XElement smses = new XElement("smses");
53+
var backupTs = DateTime.Now;
54+
smses.Add(
55+
new XAttribute("backup_date", ToDroidTime(backupTs).ToString()),
56+
new XAttribute("backup_set", Guid.NewGuid().ToString()),
57+
new XAttribute("count", 0)
58+
);
59+
int count = 0;
60+
foreach (var msg in wpFormatSMS.Element("ArrayOfMessage").Elements("Message")) {
61+
var droidTs = WpTs2Droid(long.Parse(msg.Element("LocalTimestamp").Value));
62+
bool incoming = bool.Parse(msg.Element("IsIncoming").Value);
63+
bool read = bool.Parse(msg.Element("IsRead").Value);
64+
if (incoming) {
65+
smses.Add(CreateDroidSMS(msg.Element("Sender").Value, droidTs, incoming, read, msg.Element("Body").Value));
66+
count++;
67+
}
68+
else {
69+
foreach (var recepient in msg.Element("Recepients").Elements("string")) {
70+
smses.Add(CreateDroidSMS(recepient.Value, droidTs, incoming, read, msg.Element("Body").Value));
71+
count++;
72+
}
73+
}
74+
}
75+
var msgTs = backupTs.ToString("yyyyMMddHHmmss");
76+
77+
foreach (var msg in wpFormatMMS.Element("ArrayOfMessage").Elements("Message")) {
78+
var mms = new XElement("mms");
79+
var droidTs = WpTs2Droid(long.Parse(msg.Element("LocalTimestamp").Value));
80+
bool incoming = bool.Parse(msg.Element("IsIncoming").Value);
81+
bool read = bool.Parse(msg.Element("IsRead").Value);
82+
XElement addrs = new XElement("addrs");
83+
if (incoming) {
84+
mms.Add(new XAttribute("address", msg.Element("Sender").Value));
85+
addrs.Add(
86+
new XElement("addr",
87+
new XAttribute("address", msg.Element("Sender").Value),
88+
new XAttribute("type", "137"),
89+
new XAttribute("charset", "106")
90+
),
91+
new XElement("addr",
92+
new XAttribute("address", "insert-address-token"),
93+
new XAttribute("type", "151"),
94+
new XAttribute("charset", "106")
95+
)
96+
);
97+
}
98+
else {
99+
string[] addresses = msg.Element("Recepients").Elements("string").Select(s => s.Value).ToArray();
100+
mms.Add(new XAttribute("address", string.Join("~", addresses)));
101+
addrs.Add(
102+
new XElement("addr",
103+
new XAttribute("address", "insert-address-token"),
104+
new XAttribute("type", "137"),
105+
new XAttribute("charset", "106")
106+
)
107+
);
108+
foreach(var address in addresses) {
109+
addrs.Add(
110+
new XElement("addr",
111+
new XAttribute("address", address),
112+
new XAttribute("type", "151"),
113+
new XAttribute("charset", "106")
114+
)
115+
);
116+
}
117+
}
118+
if (!string.IsNullOrWhiteSpace(msg.Element("Body").Value))
119+
mms.Add(new XAttribute("body", msg.Element("Body").Value));
120+
mms.Add(
121+
new XAttribute("contact_name", "(Unknown)"),
122+
new XAttribute("ct_cls", "null"),
123+
new XAttribute("ct_l", "null"),
124+
new XAttribute("ct_t", "application/vnd.wap.multipart.related"),
125+
new XAttribute("d_rpt", "128"),
126+
new XAttribute("d_tm", "null"),
127+
new XAttribute("date", droidTs.ToString()),
128+
new XAttribute("date_sent", "0"),
129+
new XAttribute("exp", "null"),
130+
new XAttribute("locked", "0"),
131+
new XAttribute("m_cls", "0"),
132+
new XAttribute("m_id", $"msg{count:D5}-{msgTs}@localhost"),
133+
new XAttribute("m_size", "0"), // will be assigned forward
134+
new XAttribute("m_type", "132"),
135+
new XAttribute("msg_box", "1"),
136+
new XAttribute("pri", "129"),
137+
new XAttribute("read", read ? "1" : "0"),
138+
new XAttribute("read_status", "null"),
139+
new XAttribute("readable_date", FromDroidTime(droidTs).ToString("G")),
140+
new XAttribute("resp_st", "null"),
141+
new XAttribute("resp_txt", "null"),
142+
new XAttribute("retr_st", "null"),
143+
new XAttribute("retr_txt", "null"),
144+
new XAttribute("retr_txt_cs", "null"),
145+
new XAttribute("rpt_a", "null"),
146+
new XAttribute("rr", "129"),
147+
new XAttribute("seen", read ? "1" : "0"),
148+
new XAttribute("st", "null"),
149+
new XAttribute("sub", "null"),
150+
new XAttribute("sub_cs", "null"),
151+
new XAttribute("sub_id", "null"),
152+
new XAttribute("text_only", "null"),
153+
new XAttribute("tr_id", $"id1_{count:D5}"),
154+
new XAttribute("v", "18")
155+
);
156+
var parts = new XElement("parts");
157+
int m_size = 0;
158+
int partId = 1;
159+
XElement smil = null;
160+
int dataCount = 0;
161+
foreach (var attachment in msg.Element("Attachments").Elements("MessageAttachment")) {
162+
var contentType = attachment.Element("AttachmentContentType").Value;
163+
var data = attachment.Element("AttachmentDataBase64String").Value;
164+
switch (contentType) {
165+
case "application/smil":
166+
smil = new XElement("part",
167+
new XAttribute("cd", "null"),
168+
new XAttribute("chset", "null"),
169+
new XAttribute("cid", "null"),
170+
new XAttribute("cl", "01smil"), // will get replaced at the loop end
171+
new XAttribute("ct", contentType),
172+
new XAttribute("ctt_s", "null"),
173+
new XAttribute("ctt_t", "null"),
174+
new XAttribute("fn", "null"),
175+
new XAttribute("name", "null"),
176+
new XAttribute("seq", "-1"),
177+
new XAttribute("text", Encoding.Unicode.GetString(Convert.FromBase64String(data)))
178+
);
179+
parts.Add(smil);
180+
break;
181+
case "text/plain": {
182+
var text = Encoding.Unicode.GetString(Convert.FromBase64String(data));
183+
m_size += Encoding.UTF8.GetBytes(text).Length;
184+
parts.Add(new XElement("part",
185+
new XAttribute("cd", "null"),
186+
new XAttribute("chset", "106"),
187+
new XAttribute("cid", "null"),
188+
new XAttribute("cl", $"Text{partId:D2}.txt"),
189+
new XAttribute("ct", contentType),
190+
new XAttribute("ctt_s", "null"),
191+
new XAttribute("ctt_t", "null"),
192+
new XAttribute("fn", "null"),
193+
new XAttribute("name", "null"),
194+
new XAttribute("seq", "0"),
195+
new XAttribute("text", text)
196+
));
197+
break;
198+
}
199+
default:
200+
m_size += Convert.FromBase64String(data).Length;
201+
parts.Add(new XElement("part",
202+
new XAttribute("cd", "null"),
203+
new XAttribute("chset", "null"),
204+
new XAttribute("cid", "null"),
205+
new XAttribute("cl", $"Image{partId:D2}.txt"),
206+
new XAttribute("ct", contentType),
207+
new XAttribute("ctt_s", "null"),
208+
new XAttribute("ctt_t", "null"),
209+
new XAttribute("data", data),
210+
new XAttribute("fn", "null"),
211+
new XAttribute("name", "null"),
212+
new XAttribute("seq", "0"),
213+
new XAttribute("text", "null")
214+
));
215+
dataCount++;
216+
break;
217+
}
218+
partId++;
219+
}
220+
if (smil != null)
221+
smil.Attribute("cl").Value = $"{dataCount:D2}smil";
222+
mms.Attribute("m_size").Value = m_size.ToString();
223+
mms.Add(parts);
224+
mms.Add(addrs);
225+
smses.Add(mms);
226+
count++;
227+
}
228+
smses.Attribute("count").Value = count.ToString();
229+
droidFormat = new XDocument(smses);
230+
}
231+
232+
public static void ConvertToWindowsPhone(XDocument droidFormat, out XDocument wpFormatSMS) {
233+
var smses = droidFormat.Element("smses");
234+
235+
var smsResult = new XElement(XNamespace.Xmlns + "ArrayOfMessage",
236+
new XAttribute(XNamespace.Xmlns + "xsd", XNamespace.Get("http://www.w3.org/2001/XMLSchema")),
237+
new XAttribute(XNamespace.Xmlns + "xsi", XNamespace.Get("http://www.w3.org/2001/XMLSchema-instance"))
238+
);
239+
foreach (var sms in smses.Elements("sms")) {
240+
var msg = new XElement("Message");
241+
switch (int.Parse(sms.Attribute("type").Value)) {
242+
case 1: // inbox
243+
msg.Add(new XElement("Recepients"));
244+
msg.Add(new XElement("Body", sms.Attribute("body").Value));
245+
msg.Add(new XElement("IsIncoming", true));
246+
msg.Add(new XElement("IsRead", int.Parse(sms.Attribute("read").Value) != 0));
247+
msg.Add(new XElement("Attachments"));
248+
msg.Add(new XElement("LocalTimestamp", DroidTs2Wp(long.Parse(sms.Attribute("date_sent").Value))));
249+
msg.Add(new XElement("Sender", sms.Attribute("address").Value));
250+
break;
251+
case 2: // outbox
252+
msg.Add(new XElement("Recepients", new XElement("string", sms.Attribute("address").Value)));
253+
msg.Add(new XElement("Body", sms.Attribute("body").Value));
254+
msg.Add(new XElement("IsIncoming", false));
255+
msg.Add(new XElement("IsRead", int.Parse(sms.Attribute("read").Value) != 0));
256+
msg.Add(new XElement("Attachments"));
257+
msg.Add(new XElement("LocalTimestamp", DroidTs2Wp(long.Parse(sms.Attribute("date_sent").Value))));
258+
msg.Add(new XElement("Sender"));
259+
break;
260+
default:
261+
throw new ArgumentException("Invalid message type");
262+
}
263+
smsResult.Add(msg);
264+
}
265+
wpFormatSMS = new XDocument(smsResult);
266+
}
267+
268+
static void Main(string[] args) {
269+
const string smsBackup = @".\smsBackup\Сб, июл 29 2017, 18-49-28 .msg";
270+
const string mmsBackup = @".\mmsBackup\Сб, июл 29 2017, 18-50-14 .msg";
271+
const string droidBackup = @".\";
272+
XDocument result;
273+
ConvertToAndroid(XDocument.Parse(File.ReadAllText(smsBackup)), XDocument.Parse(File.ReadAllText(mmsBackup)), out result);
274+
File.WriteAllText(droidBackup + $"sms-{DateTime.Now:yyyyMMddHHmmss}.xml", result.ToString());
275+
}
276+
}
277+
}

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Convert SMS and MMS from Windows Phone "backup+restore" app format to Android "SMS Backup & Restore" format.
2+
3+
Alpha version. You warned.

wp2droid4sms.csproj

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp1.1</TargetFramework>
6+
</PropertyGroup>
7+
8+
</Project>

wp2droid4sms.sln

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.26430.15
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "wp2droid4sms", "wp2droid4sms.csproj", "{CF510C82-8805-42B9-BBAD-8B0DF6FE5DC1}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{CF510C82-8805-42B9-BBAD-8B0DF6FE5DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{CF510C82-8805-42B9-BBAD-8B0DF6FE5DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{CF510C82-8805-42B9-BBAD-8B0DF6FE5DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{CF510C82-8805-42B9-BBAD-8B0DF6FE5DC1}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
EndGlobal

0 commit comments

Comments
 (0)