Browsed by
Month: November 2012

Un point sur les captures d'écran de vos applications WP8

Un point sur les captures d'écran de vos applications WP8

Si vous le ne savez pas encore, sachez que si vous soumettez une application Windows Phone 8, il vous sera demandé de soumettre des captures d’écran pour les 3 résolutions du système : WVGA, WXGA et 720p.

Personnellement, je regrette que le store ne se contente pas d’une seule capture WXGA pour les résolutions WVGA et WXGA, les deux résolutions ayant les mêmes ratios et soyons honnête, très rare seront les applications qui géreront différemment les deux résolutions.

Pour la résolution 720p ceci est au contraire totalement compréhensible, le ratio de l’écran étant différent, vous pourrez afficher plus d’informations sur vos écrans : un ruban en plus sur Fuse, plus d’épisodes sur TVShow ou encore plus de contenu sur Wikipedia, etc…

Si on outrepasse le désagrément de devoir faire 3 fois les captures et ce pour chaque langue (ou seulement 2 fois en lançant un batch de redimensionnement pour passer les captures WXGA en WVGA, ce qui devrait avoir très peu d’impact sur la qualité des captures), on va étudier comment faire ces captures et surtout les problématiques que l’on peut rencontrer.

Read More Read More

Bien gérer ses tuiles cycliques

Bien gérer ses tuiles cycliques

Lors du développement d’une de mes applications, je me suis rendu compte que les tuiles cycliques ne sont pas si facile que cela à gérer. En effet, il existe une petite subtilité non documentée assez importante à connaitre.

Imaginons que mon application possède en tuile principale une tuile cyclique. Je peux alors écrire :


var data = new CycleTileData() {
  CycleImages = new List<Uri>() {
    new Uri("/Images/Tile1.png", UriKind.Relative),
    new Uri("/Images/Tile2.png", UriKind.Relative),
    new Uri("/Images/Tile3.png", UriKind.Relative),
    new Uri("/Images/Tile4.png", UriKind.Relative),
    new Uri("/Images/Tile5.png", UriKind.Relative),
    new Uri("/Images/Tile6.png", UriKind.Relative)
  }
};

ShellTile.ActiveTiles.First().Update(data);

Ma tuile affiche bien les 6 images que je lui ai spécifiées et ce de façon cyclique.

Maintenant mettons à jour notre tuile, non pas avec 6 images, mais avec 1 seule. Pour cela je pense devoir écrire :


var data = new CycleTileData()
  { CycleImages = new List<Uri>() {
     new Uri("/Images/TileAlternative.png", UriKind.Relative)
  };
ShellTile.ActiveTiles.First().Update(data);

Je m’attends alors à ne voir qu’une seule image, mais ce n’est malheureusement pas le cas ! En effet je me retrouve avec le cycle suivant :

TileAlternative -> Tile2 -> Tile3 -> Tile4 -> Tile5 -> Tile6

Que s’est il passé ?

En fait, le système n’a pas remplacé le cycle par notre nouvelle série, mais a mis à jour le cycle précédent avec les données de notre nouvelle série. Par conséquent, il n’a fait que remplacer la première image en gardant le reste.

Pourquoi ce comportement ?

En fait, c’est plutôt intelligent, cela nous permet de mettre à jour qu’une partie de nos images à jour, on pourrait donc imaginer écrire le code suivant pour remplacer uniquement la 3ème et la 5ème image de notre série.


var data = new CycleTileData() {
    CycleImages = new List<Uri>() {
       null,
       null,
       new Uri("/Images/TileAlternative.png", UriKind.Relative),
       null,
       new Uri("/Images/TileAlternative2.png", UriKind.Relative)
    };
ShellTile.ActiveTiles.First().Update(data);

Dans ce cas, la valeur null indiquant qu’il faut garder les images précédentes.

Mais alors comment mettre à jour l’ensemble de notre cycle ?

Pour cela, il ne faut pas utiliser la valeur null, mais “l’uri chaîne vide”, c’est à dire :

new Uri(“”,UriKind.Relative);

Dans notre cas, nous avons 6 images, il nous suffit alors de créer une collection avec en tête notre nouvelle image suivi de 5 uri “chaîne vide” :


var data = new CycleTileData() {
     CycleImages = new List<Uri>() {
        new Uri("/Images/TileLogo.png", UriKind.Relative),
        new Uri("", UriKind.Relative),
        new Uri("", UriKind.Relative),
        new Uri("", UriKind.Relative),
        new Uri("", UriKind.Relative),
        new Uri("", UriKind.Relative)
    }
};

ShellTile.ActiveTiles.First().Update(data);

Bon dev !

Détecter qu'une musique est déjà joué en tâche de fond

Détecter qu'une musique est déjà joué en tâche de fond

Si votre application joue une musique (comme iDaft), il est très important de s’assurer que le téléphone ne joue pas déjà un son en tâche de fond.

Lorsque vous voudrez jouer une musique ou un son via le MediaPlayer, la musique précédente va s’arrêter et c’est votre application qui aura maintenant la main sur le player.

Or ce scénario est une cause de refus sur le Store, vous êtes obligé de prévenir l’utilisateur que vous allez couper sa musique pour jouer vos propres sons.

 

6.5.1 – Initial launch functionality When the user is already playing music on the phone when the app is launched, the app must not pause, resume, or stop the active music in the phone MediaQueue by calling the Microsoft.Xna.Framework.Media.MediaPlayer class.

If the app plays its own background music or adjusts background music volume, it must ask the user for consent to stop playing/adjust the background music (e.g. message dialog or settings menu). This prompt must occur each time the app launches, unless there is an opt-in setting provided to the user and the user has used this setting to opt-in.

  1. Play a music file.
  2. Launch your app.
  3. Verify that while the app loads, it does not pause, resume or stop the actively playing music.

Comment tester que le player joue déjà une musique ?

Tout simplement en utilisant la classe MediaPlayer accessible via Xna.


Microsoft.Xna.Framework.Media.MediaPlayer.GameHasControl

Rappelons que si DirectX est bien arrivé sur Windows Phone 8, XNA est toujours présent.

Il nous reste donc à ajouter une conditionnelle et à afficher une boite de dialogue et le tour est joué !


if (!Microsoft.Xna.Framework.Media.MediaPlayer.GameHasControl)
{
if (MessageBox.Show("L'application va couper votre musique, êtes-vous d'accord ?", "iDaft", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel)
{
return;
}

//jouer quelque chose
}

Bug TFS avec Windows Phone 7 : Deployment optimization failed with error : 'Access to the path XXXX is denied'.

Bug TFS avec Windows Phone 7 : Deployment optimization failed with error : 'Access to the path XXXX is denied'.

Comme vous le savez surement, il y a deux types de synchronisation TFS pour les espaces de travail : local et serveur

Différence entre local et serveur

Dans la plupart des cas, local est préférable car elle offre plusieurs avantages : elle apporte notamment la possibilité d’effectuer des opérations même lorsque vous n’êtes pas connecté à internet. Un autre avantage du mode local est la possibilité de restaurer les fichiers supprimés même sans connexion.

De son côté, le mode serveur possède aussi des avantages, notamment une empreinte mémoire plus faible (pas de copie de fichiers) et plus performant lorsque vous avez de nombreux fichiers versionnés.

Attention avec les projets Windows Phone 7 !!!

Si vous utilisez le SDK WP8 pour développer des applications WP7, le mode local sera très fortement conseillé. En effet, il existe un petit bug très gênant dans le SDK actuel qui vous empêchera de travailler correctement lorsque vous déploierez vos projets sur un windows phone 8.

Explication du bug

Lorsque vous déployez une application WP7 sur un Windows Phone 8, visual studio va optimiser votre application avant le déploiement en utilisant NGEN. Visual va alors créer un répertoire MDIL dans Debug/Release, copier le fichier WMAppManifest.xml, optimiser les DLLs de votre applications et recréer le XAP.

Or, comme votre fichier WMAppManifest.xml étant en lecture seule car synchronisé de façon serveur, visual studio va le copier dans le répertoire MDIL tout en le laissant en lecture seule, ce qui est uniquement le cas avec un projet WP7.

Si vous modifiez votre projet, visual studio va de nouveau vouloir copier le fichier WMAppManifest.xml et lancer une erreur car il n’arrivera pas à écrire dedans.

 

Error : Deployment optimization failed with error : ‘Access to the path XXXX is denied’. Please rebuild the project and try again

Une solution pourrait être d’enlever le mode lecture du fichier après chaque rebuild, mais ceci est plutôt contraignant ou tout simplement de passer l’espace de travail TFS en mode local, ce qui supprimerait le mode lecture seul et donc éviterait le problème.

Comment passer du mode serveur au mode local ?

Lancez visual studio et aller dans le menu :

File > Source Control > Advanced > Workspaces…

 

Une boite de dialogue “Manage Workspaces” va alors s’afficher, cliquez sur le bouton “Edit”

Cliquez sur le bouton “Advanced”

Enfin dans la liste déroulante “Location”, choisissez “Local” :

Et cliquez sur le bouton valider. Visual Studio va alors passer l’ensemble de vos projets en local. L’opération peut prendre quelques secondes.

Et voilà ! Vos fichiers ne sont plus en lecture seul, vous n’aurez plus de soucis avec visual lorsque vous perdrez votre connexion et surtout, vous n’aurez plus de problème avec vos projets WP7 !

 

 

Conseil sur la décompilation et désobfuscation pour Visual 2012

Conseil sur la décompilation et désobfuscation pour Visual 2012

Cet article fait suite à mon précédent article sur la désobfuscation :
http://www.rudyhuyn.com/blog/2012/07/23/conseils-sur-la-decompilation-et-la-desobfuscation

Visual Studio 2012 apporte de nombreuses améliorations, mais aussi quelques modifications, notamment les expressions régulières dans le bloc de recherche qui ont changées.

Voici donc une réédition de l’article précédent en prenant en compte le nouveau format des expressions régulières.

Il y a quelques mois, je me suis fait volé mon pc portable avec quelques codes sources de projet que je n’avais pas encore sauvegardé car sans internet (quasiment 10 jours de travail de perdu).

Toutefois j’avais encore les xap à ma disposition ce qui m’a aidé a récupérer le code sources (offusqué) de l’app. Pour être honnête, même offusqué, en une dizaine d’heures et avec quelques aspirines, vous pouvez facilement revenir à l’original.

Voilà pour information à quoi ressemble un code offusqué une fois décompilé :

public class YoutubeHelper
{
[CompilerGenerated]
private sealed class a
{
private sealed class a
{
public YoutubeHelper.a a;
public List b;
public void c()
{
if (this.a.c)
{
this.a.b.Videos.Clear();
}
using (List.Enumerator enumerator = this.b.GetEnumerator())
{
while (enumerator.MoveNext())
{
YoutubeVideo current = enumerator.get_Current();
this.a.b.Videos.Add(current);
}
}

}
}
public HttpWebRequest a;
public YoutubeHelper b;
public bool c;
public void d(IAsyncResult A_0)
{
YoutubeHelper.a.a a = new YoutubeHelper.a.a();
a.a = this;
Stream responseStream = this.a.EndGetResponse(A_0).GetResponseStream();
a.b = this.b.ParseYoutubeData(responseStream);
if (a.b != null)
{
Deployment.get_Current().get_Dispatcher().BeginInvoke(new Action(a.c));
this.b._youtubeCurrentPage = 0;
}
}
}
[CompilerGenerated]
private bool b;
[CompilerGenerated]
private string c;
[CompilerGenerated]
private ObservableCollection d;

public void LoadYoutube(bool clear)
{
AsyncCallback asyncCallback = null;
YoutubeHelper.a a = new YoutubeHelper.a();
a.c = clear;
a.b = this;
this.Loading = true;
this.RaisePropertyChanged("Loading");
string text = string.Format("http://gdata.youtube.com/feeds/api/videos?q={0}&start-index={1}&max-results=20&v=2", this.Search, this._youtubeCurrentPage);
a.a = (HttpWebRequest)WebRequest.Create(new Uri(text, 1));
try
{
WebRequest arg_73_0 = a.a;
if (asyncCallback == null)
{
asyncCallback = new AsyncCallback(a.d);
}
arg_73_0.BeginGetResponse(asyncCallback, null);
}
catch (Exception)
{
}
}

}

En gros : ca pique les yeux, c’est difficilement lisible mais avec un peu de courage, ça se simplifie et surtout, ca ne compile pas.

Voilà donc un petit guide pour vous aider à la tâche !

Reconstruire vos pages et vos usercontrols

Imaginons que vous avez une page nommée MainPage.xaml (qui ne l’a pas ???)

Lorsque vous allez décompiler votre projet, vous allez avoir deux fichiers : mainpage.xaml et MainPage.cs. Commençons par les renommer correctement : MainPage.xaml et MainPage.xaml.cs et mettez Page en tant que Build Action pour votre page xaml.

Allons maintenant dans le code behind et indiquons notre classe comme partial. Supprimons le code qui a été auto-généré lors de la compilation initiale:

  • Supprimez la fonction InitializeComponent
  • Repérez les variables correspondants à votre élément XAML, ceci est plutôt simple, il précède la variable “private bool _contentLoaded” et sont indiqué comme internal :
  • Supprimez la variable _contentLoaded

Vos deux fichiers devraient maintenant être liés.

Toutefois vous remarquerez que visual ne fusionne pas les deux fichiers comme d’habitude :

Pour cela, pas besoin de s’embêter à éditer le fichier vcproj, installez (si ce n’est pas déjà fait) VSCommands (ici : http://visualstudiogallery.msdn.microsoft.com/d491911d-97f3-4cf6-87b0-6a2882120acf/)

Sélectionnez les deux éléments puis faites un clic droit et sélectionnez “Group Items”

Vous voila maintenant avec les deux fichiers regroupés comme d’origine.

Gérer les lambdas

Pour résumer le travail, toutes les lamba expressions ont été converti par le compilateur en classe interne, il faut donc les retransformer tout en faisant attention à un point : les classes sont nommés comme les variables et les fonctions soient : a, b, c … un même nom peut donc représenter à la fois une classe interne, une fonction et une variable, autant vous dire que la fonction rename de visual s’y perd.

Les tableaux

Voilà à quoi ressemble un tableau une fois décompilé :

accesseur :

int i = a[20];

=> int i = a.get_Item(0);

affectation :

a[20]=3;

=> a.set_Item(20,3);

Voici donc quelques expressions régulières pour vous aider :

replace .get_Item(([^)(]+)) par [$1]

et

replace .set_Item(([^),(]+),([^),(]+)) par [$1] = $2

Attention, ces expressions régulières ne sont pas parfaites, elles ne prennent pas les expressions contenant des parenthèses à l’intérieur comme : a[toto(12)]. Faites donc une recherche de set_Item et de get_item après coup pour gérer les cas restants.

Les propriétés

Un des points positifs lorsque l’on doit décompiler un programme est que les propriétés même après offuscation, garde leurs noms (sinon il serait impossible de gérer les RaisePropertyChanged par exemple), toutefois, les décompilateurs du marché ont un peu de problèmes avec, par exemple :

MyGrid.Width=200;

deviendra :

MyGrid.set_Width(200);

ou encore :

int i=MyGrid.Width;

deviendra

int a=MyGrid.get_Width();

il faudra donc passer un peu de temps pour réécrire l’ensemble des affections ou utilisations des propriétés. Voici quelques expression régulière pour vous aider dans cette tâche (à n’utiliser que si vous n’avez pas de fonction nommées set_XXX ou get_XXX)

Modifier les accesseurs :

.get_([^(]+)() en .$1

De même pour modifier les affectateurs

.set_([^(]+) en .$1=

Enregistrement et dés-enregistrement d’event handler

Après décompilations, vos events vont ressembler à cela :


contacts.add_SearchCompleted(delegate(object senderr, ContactsSearchEventArgs er)

au lieu de :


contacts.SearchCompleted+=(delegate(object senderr, ContactsSearchEventArgs er)

de même pour le dés-enregistrement.

Voici donc deux expressions régulières pour corriger cela.

enregistrement :

replacez .add_([^(]+) par .$1+=

dés-enregistrement :

replacez .remove_([^(]+) par .$1-=

 

Les affectations multiples

Si vous écrivez :


Toto.ScaleY=Toto.ScaleX=0;

une fois compilé, offusqué et redécompilé, le code deviendra :


ScaleTransform arg_108_0 = this.Toto;
double num3;
this.Toto.set_ScaleY(num3 = 0);
double initialScale;
arg_108_0.set_ScaleX(num3);

A vous donc de supprimer l’ensemble des variables intermédiaire pour simplifier la relecture de votre code.

Les énumérations

Toutes les énumérations sont interprétées en tant qu’entier dans le code décompilé, par exemple :


new Uri("/toto.xaml,Urikind.Relative);

sera décompilé comme :


new Uri("/toto.xaml,2);

Ce qui est tout de suite moins parlant. Pour vous faciliter le travail, je conseille vivement Refactor.

Pour cela, castez votre int avec la bonne énumération :


new Uri("/toto.xaml,(UriKind)2);

Puis sélectionnez dans la marge, l’outil “Compute constant value”.

Refactor se chargera alors automatiquement de trouver la bonne valeur pour l’énumération. Cela est encore plus intéressant quand on sait qu’il gère aussi automatiquement les OR binaire :

(RegexOptions) 5

Deviendra alors :

RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.None

Override & virtual

Attention, parfois les méthodes override seront vu comme des méthodes virtuelles par le décompilateur, ce qui signifie qu’elles ne surchargeront pas parfois les méthodes initiales, par exemple :

protected virtual void OnNavigatedTo(NavigationEventArgs e)

ne sera jamais appelé contrairement à

protected override” void OnNavigatedTo(NavigationEventArgs e)

Gestion de la localisation

Une des difficultés que j’ai rencontré est de récupérer les localisations de mes apps.

Lorsque vous allez décompiler votre dll, vous aller trouver un fichier XXX.resources. L’extension resources correspond à la version binaire des fichiers resx de votre projet. Il est assez facile de le reconvertir en resx, grâce à l’outils resgen.exe inclut dans l’installation de visual studio.

Ouvrez “Visual Studio Command Prompt” et tapez la commande suivante :

resgen AppResources.resources AppResources.resx

(où AppResources correspond à votre fichier ressources)

Lorsque l’on ouvre le fichier resx généré, on se rend compte qu’il ne contient que la localisation par défaut de votre application.

Où trouver les autres localisations ?

Ouvrez de nouveau votre xap, vous remarquerez des dossiers fr, es, de…

Dans ces dossiers vous allez trouver des fichiers resources.dll

Si on décompile ce fichier, on peut voir :

que celle-ci contient le fichier “.resources” propre à une langue. Il nous reste donc à le sauvegarder, à le convertir en resx comme auparavant et de faire ceci pour chaque langue.